可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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 function as follows:
def static_var(varName, value):
def decorate(function):
setattr(function,varName,value)
return function
return decorate
As the example shows, this can be used to attach a variable to the function:
@static_var('seed', 0)
def counter():
counter.seed +=1
return counter.seed
This method will return the number of times it has been called.
The issue i am having is that this does not work if I define the method inside a class:
class Circle(object):
@static_var('seed',0)
def counter(self):
counter.seed +=1
return counter.seed
If I instantiate a Circle
and run counter
,
>>>> myCircle = Circle()
>>>> myCircle.counter()
I get the following error: NameError: global name 'counter' is not defined
.
My response to this was that maybe I need to use self.counter
, i.e.
class Circle(object):
@static_var('seed',0)
def counter(self):
self.counter.seed +=1
return self.counter.seed
However this produces the error, AttributeError: 'instancemethod' object has no attribute 'seed'
.
What is going on here?
回答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)
def counter(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)
def counter(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.
回答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 += 1
return counter
counter.seed = 0
And in the second case, you can just as easily put the "function state" in the class.
class Circle(object):
seed = 0
# if you want the count to be unique per instance
def counter_inst(self):
self.seed += 1
return self.seed
# if you want the count to be shared between all instances of the class
@classmethod
def counter_cls(cls):
cls.seed += 1
return cls.seed
回答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:
def static_var(var_name, value):
def decorator(function):
setattr(function, var_name, value)
return function
return decorator
# apply it to method
class Circle(object):
@static_var('seed', 0)
def counter(self):
counter_method = Circle.counter.__get__(self, Circle).__func__ # added
counter_method.seed +=1
return counter_method.seed
myCircle = Circle()
print(myCircle.counter()) # 1
print(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:
def static_var(var_name, value):
def decorator(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 it
return function
else:
static_vars = type('Statics', (object,), {})() # create container
setattr(static_vars, var_name, value) # add first var to it
def decorated(*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 function
def counter(static_vars):
static_vars.seed +=1
return static_vars.seed
print(counter()) # 1
print(counter()) # 2
class Circle(object):
@static_var('seed', 0) # apply it to a method
def counter(static_vars, self):
static_vars.seed +=1
return static_vars.seed
myCircle = Circle()
print(myCircle.counter()) # 1
print(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)
def counter2(static_vars):
static_vars.seed += 1
static_vars.offset *= 2
return static_vars.seed + static_vars.offset
print(counter2()) # 1 + 2*42 = 85
print(counter2()) # 2 + 2*84 = 170
回答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)
def funccounter(statics, add=1):
statics.seed += add
return statics.seed
print funccounter() #1
print funccounter(add=2) #3
print funccounter() #4
class ACircle(object):
@static_var2('seed',0)
def counter(statics, self, add=1):
statics.seed += add
return statics.seed
c = ACircle()
print c.counter() #1
print c.counter(add=2) #3
print c.counter() #4
d = ACircle()
print d.counter() #5
print d.counter(add=2) #7
print d.counter() #8
If you like the usage, here's the implementation:
class StaticMan(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
def static_var2(name, val):
def decorator(original):
if not hasattr(original, ':staticman'):
def wrapped(*args, **kwargs):
return original(getattr(wrapped, ':staticman'), *args, **kwargs)
setattr(wrapped, ':staticman', StaticMan())
f = wrapped
else:
f = original #already wrapped
getattr(f, ':staticman')[name] = val
return f
return decorator