类中的函数实例变量

时间:2013-10-01 20:42:30

标签: python oop

我正在尝试在我的方法中实现一个所谓的静态变量,类似于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'

这里发生了什么?

4 个答案:

答案 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中,仍然会返回一个未绑定的方法对象(没有附加实例的方法)。

当然,仅仅因为你可以做到这一点,并不一定会让它成为一个好主意。使用方法,您有一个类,为您提供存储该计数器的替代位置。您可以将其放在Countertype(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