1

我试图在我的方法中实现一个所谓的静态变量,类似于这个 Stackoverflow 线程中描述的装饰器方法。具体来说,我定义了一个装饰器函数如下:

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

如果我实例化 aCircle并运行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'.

这里发生了什么?

4

4 回答 4

6

您想访问函数对象,但您访问的是方法。Python 将实例和类上的函数视为描述符,在查找时返回绑定方法。

利用:

@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),后者将为您提供每个子类的计数器。

于 2013-10-01T20:45:31.970 回答
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
于 2013-10-01T20:52:30.550 回答
2

问题是类方法是描述符对象,而不是函数。如果您在方法中做更多的工作,您可以对这两种类型的可调用对象使用相同的装饰器,在包含 v3.x 的 Python v2.6 中。这就是我的意思:

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
于 2013-10-01T21:37:34.890 回答
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
于 2013-10-01T21:08:23.753 回答