Ziofil
Ziofil

Reputation: 2107

How to avoid infinite recursion in python class method

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

Answers (3)

user2357112
user2357112

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

claytond
claytond

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

juanpa.arrivillaga
juanpa.arrivillaga

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

Related Questions