DimG
DimG

Reputation: 1781

Behavioural difference between decorated function and method in Python

I use the following workaround for "Pythonic static variables":

def static_vars(**kwargs):
    """decorator for funciotns that sets static variables"""
    def decorate(func):
        for k, v in kwargs.items():
            setattr(func, k, v)
        return func
    return decorate

@static_vars(var=1)
def global_foo():
    _ = global_foo
    print _.var
    _.var += 1

global_foo()  # >>> 1
global_foo()  # >>> 2

It works just as supposed to. But when I move such a decorated function inside a class I get a strange change:

class A(object):
    @static_vars(var=1)
    def foo(self):
        bound = self.foo
        unbound = A.foo
        print bound.var  # OK, print 1 at first call
        bound.var += 1   # AttributeError: 'instancemethod' object has no attribute 'var'

    def check(self):
        bound = self.foo
        unbound = A.foo
        print 'var' in dir(bound)
        print 'var' in dir(unbound)
        print bound.var is unbound.var  # it doesn't make much sense but anyway

a = A()
a.check()  # >>> True
           # >>> True
           # >>> True

a.foo()    # ERROR

I can not see what causes such behaviour. It seems to me that it has something to do with python descriptors protocol, all that bound vs unbound method stuff. Somehow the foo.var attribute is accessible but is not writable.

Any help is appreciated.

P.S. I understand that static function variables are essentially class variables and this decorator is unnecessary in the second case but the question is more for understanding the Python under the hood than to get any working solution.

Upvotes: 1

Views: 100

Answers (1)

Jacob Zimmerman
Jacob Zimmerman

Reputation: 1573

a.foo doesn't return the actual function you defined; it returns a bound method of it, which wraps up the function and has self assigned toa.

https://docs.python.org/3/howto/descriptor.html#functions-and-methods

That guide is out of date a little, though, since unbound methods just return the function in Python 3.

So, to access the attributes on the function, you need to go through A.foo (or a.foo.__func__)instead of a.foo. And this will only work in Python 3. In Python 2, I think A.foo.__func__ will work.

Upvotes: 1

Related Questions