Reputation: 2107
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?
Upvotes: 1
Views: 727
Reputation: 281683
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.)
Upvotes: 1
Reputation: 1099
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.
Upvotes: 1
Reputation: 96236
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
Upvotes: 1