1

This is a follow-up question from this.

I want a class that handles a function that can update itself. This is a simplified example, but I still end up with an infinite recursion:

def f(x):
    return x

class func:

    def __init__(self, f):
        self.f = f

    def translate(self, c):
        def f_(x):
            return self.f(x + c)
        self.f = f_

It works only once:

>>> myfunc = func(f)
>>> myfunc.f(1)
1
>>> myfunc.translate(5)
>>> myfunc(1)
...
RecursionError: maximum recursion depth exceeded

The problem is that self.f calls self.f, which would not happen if translate were defined outside of a class:

def translate(f, c):
    def f_(x):
        return f(x+c)
    return f_

This works:

>>> f = translate(f, 5)
>>> f(1)
6
>>> f = translate(f,-5)
>>>f(1)
1

How can I make it work inside the class?

3 Answers 3

1

If you'd tried to write your outside-a-class translate closer to how you wrote your first translate:

def f(x):
    return x
def translate(c):
    global f
    def f_(x):
        return f(x+c)
    f = f_
translate(5)
f(1)

you would have gotten a RecursionError there too. Your outside-a-class translate worked because its f_ looks for f in a local variable that doesn't get overwritten, rather than in the attribute or global you're about to rebind to your new f_.

Have your translate method look in a local variable too:

def translate(self, c):
    f = self.f
    def f_(self, x):
        return f(x+c)
    self.f = f_

(By the way, call this method enough times and you'll stack up so many layers of wrappers that you hit the recursion limit anyway. Stacking wrappers indefinitely is a bad idea.)

Sign up to request clarification or add additional context in comments.

3 Comments

That's an insightful point. Is stacking wrappers avoidable somehow?
@Ziofil: For this case, you could just keep track of the offset on the instance. For more general transformations, no.
Depends on your requirements. If you're going to apply the wrappers sequentially, you can keep and loop through a list of methods. If you need to affect a value going both "down" and "up", you would need a more complicated solution (e.g. a beforeNested and afterNested method).
1

Just use a closure, like you are doing without the class, by getting a reference to the original function object before updating the self.f attribute:

In [1]: def f(x):
   ...:     return x
   ...:
   ...: class func:
   ...:
   ...:     def __init__(self, f):
   ...:         self.f = f
   ...:
   ...:     def translate(self, c):
   ...:         f = self.f
   ...:         def f_(x):
   ...:             return f(x + c)
   ...:         self.f = f_
   ...:

In [2]: myfunc = func(f)

In [3]: myfunc.f(1)
Out[3]: 1

In [4]: myfunc.translate(5)

In [5]: myfunc.f(1)
Out[5]: 6

Comments

1

You've run into a quirk of Python name resolution. Try something like this:

def translate(self, c):
    def f_(x, f=self.f):
        return f(x + c)
    self.f = f_

I wish I understood the issue well enough to give a concise explanation. The rough version is that self.f always points to "the f method of self". When you replace the f-method of self, it points to the new function and not the old one. This is why it loops infinitely.

The kwargs trick works around the issue by creating a new variable in a special scope. The value of f in f=self.f is self-contained in the function and stays with this specific function definition. It gets set to the current value of self.f when the function is defined. As a result, it doesn't get changed to point to the circular version of the function.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.