Skip to content Skip to sidebar Skip to footer

Function Instance Variables Inside A Class

I'm trying to implement a so-called static variable in my method, similar to the decorator method described in this Stackoverflow thread. Specifically, I define a decorator functio

Solution 1:

You want to access the function object, but you are instead accessing a method. Python treats functions on instances and classes as descriptors, returning bound methods at lookup time.

Use:

@static_var('seed',0)defcounter(self):
    self.counter.__func__.seed += 1

to reach the wrapped function object.

In Python 3, you can also access the function object on the class:

@static_var('seed',0)defcounter(self):
    Circle.counter.seed += 1

In Python 2 that'd still return an unbound method object (a method without an instance attached).

Of course, just because you can do this, does not necessarily make it a good idea. With a method you have a class, giving you an alternative location to store that counter. You could put it on either Counter or on type(self), where the latter would give you a counter per subclass.

Solution 2:

What you're trying to achieve looks like something you shouldn't be doing at all.

In the first case, you can just as easily get away with a much simpler:

def counter():
    counter.seed +=1return counter
counter.seed = 0

And in the second case, you can just as easily put the "function state" in the class.

classCircle(object):
    seed = 0# if you want the count to be unique per instancedefcounter_inst(self):
        self.seed += 1returnself.seed

    # if you want the count to be shared between all instances of the class@classmethoddefcounter_cls(cls):
        cls.seed += 1return cls.seed

Solution 3:

The problem is that class methods are descriptor objects, not functions. You can use the same decorator for both types of callables, in Python v2.6 on including v3.x, if you do a little more work in methods. Here's what I mean:

defstatic_var(var_name, value):
    defdecorator(function):
        setattr(function, var_name, value)
        return function
    return decorator

# apply it to methodclassCircle(object):
    @static_var('seed', 0)defcounter(self):
        counter_method = Circle.counter.__get__(self, Circle).__func__  # added
        counter_method.seed +=1return counter_method.seed

myCircle = Circle()
print(myCircle.counter())  # 1print(myCircle.counter())  # 2

What the method version does is call the descriptor's__get__method to get a bound method instance object, and then accesses its__func__attribute to get the actual function instance which has the named attribute attached to it.

For versions of Python before 2.6, you would need to useim_funcinstead of__func__.

Update:

Most of the issues noted can be avoided by changing the decorator so that it adds an argument to the beginning of the call and writing the decorated functions to refer to that rather than to themselves to access the variables. Another nice thing is this approach works in both Python 2.x and 3.x:

defstatic_var(var_name, value):
    defdecorator(function):
        static_vars = getattr(function, 'static_vars', None)
        if static_vars:  # already have a container?setattr(static_vars, var_name, value)  # add another var to itreturn function
        else:
            static_vars = type('Statics', (object,), {})()  # create containersetattr(static_vars, var_name, value)  # add first var to itdefdecorated(*args, **kwds):
                return function(static_vars, *args, **kwds)
            decorated.static_vars = static_vars
            return decorated
    return decorator

@static_var('seed', 0)  # apply it to a functiondefcounter(static_vars):
    static_vars.seed +=1return static_vars.seed

print(counter())  # 1print(counter())  # 2classCircle(object):
    @static_var('seed', 0)  # apply it to a methoddefcounter(static_vars, self):
        static_vars.seed +=1return static_vars.seed

myCircle = Circle()
print(myCircle.counter())  # 1print(myCircle.counter())  # 2

This decorator allows adding more than one static:

@static_var('seed', 0)  # add two of them to a function@static_var('offset', 42)defcounter2(static_vars):
    static_vars.seed += 1
    static_vars.offset *= 2return static_vars.seed + static_vars.offset

print(counter2())  # 1 + 2*42 = 85print(counter2())  # 2 + 2*84 = 170

Solution 4:

May I present another alternative which might be a bit nicer to use and will look the same for both methods and functions:

@static_var2('seed',0)deffunccounter(statics, add=1):
    statics.seed += add
    return statics.seed

print funccounter()       #1print funccounter(add=2)  #3print funccounter()       #4classACircle(object):
    @static_var2('seed',0)defcounter(statics, self, add=1):
        statics.seed += add
        return statics.seed

c = ACircle()
print c.counter()      #1print c.counter(add=2) #3print c.counter()      #4
d = ACircle()
print d.counter()      #5print d.counter(add=2) #7print d.counter()      #8

If you like the usage, here's the implementation:

classStaticMan(object):
    def__init__(self):
        self.__dict__['_d'] = {}

    def__getattr__(self, name):
        return self.__dict__['_d'][name]
    def__getitem__(self, name):
        return self.__dict__['_d'][name]
    def__setattr__(self, name, val):
        self.__dict__['_d'][name] = val
    def__setitem__(self, name, val):
        self.__dict__['_d'][name] = val

defstatic_var2(name, val):
    defdecorator(original):
        ifnothasattr(original, ':staticman'):    
            defwrapped(*args, **kwargs):
                return original(getattr(wrapped, ':staticman'), *args, **kwargs)
            setattr(wrapped, ':staticman', StaticMan())
            f = wrapped
        else:
            f = original #already wrappedgetattr(f, ':staticman')[name] = val
        return f
    return decorator

Post a Comment for "Function Instance Variables Inside A Class"