简体   繁体   English

类内的函数实例变量

[英]Function instance variables inside a class

I'm trying to implement a so-called static variable in my method, similar to the decorator method described in this Stackoverflow thread . 我正在尝试在我的方法中实现一个所谓的静态变量,类似于Stackoverflow线程中描述decorator方法 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 , 如果我实例化一个Circle并运行counter

>>>> myCircle = Circle()
>>>> myCircle.counter()

I get the following error: NameError: global name 'counter' is not defined . 我收到以下错误: NameError: global name 'counter' is not defined

My response to this was that maybe I need to use self.counter , ie 我对此的回应是,也许我需要使用self.counter ,即

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' . 但这会产生错误, AttributeError: 'instancemethod' object has no attribute 'seed'

What is going on here? 这里发生了什么?

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. Python将实例和类上的函数视为描述符 ,在查找时返回绑定的方法。

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: 在Python 3中,您还可以访问类中的function对象:

@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). 在Python 2中,该方法仍会返回未绑定的方法对象(未附加实例的方法)。

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 . 您可以将其放在Countertype(self) ,后者将为您提供每个子类的计数器。

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

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. 如果在方法上做了更多工作,则可以在包含v3.x的Python v2.6中对两种类型的可调用对象使用相同的装饰器。 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. 方法版本所执行的操作是调用描述符的__get__方法以获取绑定的方法实例对象,然后访问其__func__属性以获取具有已附加命名属性的实际函数实例。

For versions of Python before 2.6, you would need to use im_func instead of __func__ . 对于2.6之前的Python版本,您需要使用im_func而不是__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: 另一件好事是这种方法在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

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM