Function Instance Variables Inside A Class
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_func
instead 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"