Переменные экземпляра объекта внутри класса
Я пытаюсь реализовать так называемую статическую переменную в моем методе, подобную методу 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