我正在尝试在我的方法中实现一个所谓的静态变量,类似于decorator method described in this Stackoverflow thread。具体来说,我定义了一个装饰器函数,如下所示:
def static_var(varName, value):
def decorate(function):
setattr(function,varName,value)
return function
return decorate
如示例所示,这可用于将变量附加到函数:
@static_var('seed', 0)
def counter():
counter.seed +=1
return counter.seed
此方法将返回被调用的次数。
我遇到的问题是,如果我在类中定义方法,这不起作用:
class Circle(object):
@static_var('seed',0)
def counter(self):
counter.seed +=1
return counter.seed
如果我实例化Circle
并运行counter
,
>>>> myCircle = Circle()
>>>> myCircle.counter()
我收到以下错误:NameError: global name 'counter' is not defined
。
我对此的回答是,我可能需要使用self.counter
,即
class Circle(object):
@static_var('seed',0)
def counter(self):
self.counter.seed +=1
return self.counter.seed
然而,这会产生错误AttributeError: 'instancemethod' object has no attribute 'seed'
。
这里发生了什么?
答案 0 :(得分:6)
您想要访问函数对象,但您正在访问一个方法。 Python将实例和类上的函数视为descriptors,在查找时返回绑定方法。
使用:
@static_var('seed',0)
def counter(self):
self.counter.__func__.seed += 1
到达包装的函数对象。
在Python 3中,您还可以访问类上的函数对象:
@static_var('seed',0)
def counter(self):
Circle.counter.seed += 1
在Python 2中,仍然会返回一个未绑定的方法对象(没有附加实例的方法)。
当然,仅仅因为你可以做到这一点,并不一定会让它成为一个好主意。使用方法,您有一个类,为您提供存储该计数器的替代位置。您可以将其放在Counter
或type(self)
上,后者会为每个子类提供一个计数器 。
答案 1 :(得分:4)
你想要达到的目标看起来像你根本不应该做的事情。
在第一种情况下,您可以轻松地使用更简单的方法:
def counter():
counter.seed += 1
return counter
counter.seed = 0
在第二种情况下,您可以轻松地将“功能状态”放在类中。
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
答案 2 :(得分:2)
问题是类方法是descriptor objects,而不是函数。如果你在方法上做了一些工作,你可以在Python v2.6中使用相同的装饰器来处理这两种类型的callables,包括v3.x。这就是我的意思:
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
方法版本所做的是调用描述符的__get__
方法来获取绑定的方法实例对象,然后访问其__func__
属性以获取具有命名的实际函数实例附属于它的属性。
对于2.6之前的Python版本,您需要使用im_func
而不是__func__
。
<强>更新强>
通过更改装饰器可以避免注意到的大多数问题,以便它在调用的开头添加一个参数,并编写装饰函数来引用它而不是自己来访问变量。另一个好处是这种方法适用于Python 2.x和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
这个装饰器允许添加多个静态:
@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
答案 3 :(得分:1)
我是否可以提出另一种可能更好用的替代方案,对于这两种方法和功能看起来都是一样的:
@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
如果您喜欢这种用法,请参阅以下内容:
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