Переменные экземпляра объекта внутри класса

Я пытаюсь реализовать так называемую статическую переменную в моем методе, подобную методу qaru.site/info/17178/.... В частности, я определяю функцию декоратора следующим образом:

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'.

Что здесь происходит?

Ответ 1

Вы хотите получить доступ к объекту функции, но вместо этого вы получаете доступ к методу. 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, который все равно будет возвращать объект unbound method (метод без прикрепленного экземпляра).

Конечно, только потому, что вы можете это сделать, это не обязательно делает его хорошей идеей. С помощью метода у вас есть класс, который дает вам альтернативное место для хранения этого счетчика. Вы можете поместить его на Counter или на type(self), где последний даст вам счетчик для каждого подкласса.

Ответ 2

То, что вы пытаетесь достичь, похоже на то, что вы не должны делать вообще.

В первом случае вы можете так же легко уйти с гораздо проще:

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

Ответ 3

Проблема в том, что методы класса объекты дескриптора, а не функции. Вы можете использовать один и тот же декоратор для обоих типов вызовов, в Python v2.6, включая 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__, чтобы получить фактический экземпляр функции, к которому прикреплен именованный атрибут.

Для версий Python до версии 2.6 вам нужно использовать im_func вместо __func__.

Update:

Большинство отмеченных проблем можно избежать, изменив декоратор, чтобы он добавил аргумент в начало вызова и написал декорированные функции, чтобы ссылаться на них, а не на самих себя, чтобы получить доступ к переменным. Еще одна приятная вещь: этот подход работает как в 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

Ответ 4

Могу ли я представить еще одну альтернативу, которая может быть немного приятнее в использовании и будет выглядеть одинаково для обоих методов и функций:

@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