2012-03-01 2 views
8

Я хотел бы добавить атрибут к методу экземпляра в одном из моих классов. Я попробовал ответить в this question, но этот ответ работает только на функции - насколько я могу судить.Добавление атрибутов в методы экземпляра в Python

В качестве примера, я хотел бы быть в состоянии сделать что-то вроде:.

class foo(object): 
    ... 
    def bar(self): 
     self.bar.counter += 1 
     return self.bar.counter 
    bar.counter = 1 
    ... 

, но, когда я называю Foo() бар() я получаю:

AttributeError: 'instancemethod' object has no attribute 'counter' 

Моя цель при этом нужно попытаться произвести впечатление на то, что переменная 'counter' является локальной для метода bar(), а также для предотвращения загромождения пространства имен класса еще одним атрибутом. Есть ли способ сделать это? Есть ли более питонический способ сделать это?

+0

Если 'counter' быть уровень класса (все экземпляры разделяют текущее значение счетчика) или уровня экземпляра (каждый экземпляр будет начинаться с 1)? –

+0

'counter' должен быть экземпляром уровня – sbell

+0

Этот связанный с этим вопрос может (или может быть) быть полезным: http://stackoverflow.com/questions/7034063/adding-attributes-the-instancemethods-in-python –

ответ

8

В Python 3 ваш код будет работать, но в Python 2 есть некоторая упаковка, что происходит, когда методы ищутся.

Класс против Instance

  • уровня Класса: хранение counter с функцией (либо непосредственно, либо с помощью изменяемого по умолчанию) фактически делает это атрибутом уровня класса, поскольку есть только когда-либо один из функции, независимо от того, сколько у вас экземпляров (все они имеют один и тот же функциональный объект).

  • уровень экземпляра: сделать counter уровень экземпляра атрибута необходимо создать функцию в __init__, затем оберните его functools.partial (так он ведет себя как обычный метод), а затем сохранить его на экземпляре - теперь вы имеют один объект функции для каждого экземпляра.

Класс Уровень

Принятая практика для статического типа переменного использовать изменяемый аргумент по умолчанию:

class foo(object): 
    ... 
    def bar(self, _counter=[0]): 
     _counter[0] += 1 
     return _counter[0] 

Если вы хотите, чтобы быть красивее, вы можете определить свой собственный изменчивые контейнер:

class MutableDefault(object): 
    def __init__(self, start=0): 
     self.value = start 
    def __iadd__(self, other): 
     self.value += other 
     return self 
    def value(self): 
     return self.value 

и изменить свой код следующим образом:

class foo(object): 
    def bar(self, _counter=MutableDefault()): 
     _counter += 1 
     return _counter.value 

уровень Instance

from functools import partial 

class foo(object): 
    def __init__(self): 
     def bar(self, _counter=MutableDefault(1)): # create new 'bar' each time 
      value = _counter.value 
      _counter += 1 
      return value 
     self.bar = partial(bar, self) 

Резюме

Как вы можете видеть, читаемость принял серьезный удар при переходе на уровне экземпляра для counter. Я настоятельно рекомендую вам переосмыслить важность подчеркивания того, что counter является частью bar, и если действительно важно, возможно, сделать bar своим собственным классом, экземпляры которого становятся частью экземпляров foo. Если это на самом деле не важно, сделать это обычным способом:

class foo(object): 
    def __init__(self): 
     self.bar_counter = 0 
    def bar(self): 
     self.bar_counter += 1 
     return self.bar_counter 
+0

Обратите внимание, что 'MutableDefault' не находится рядом с полной реализацией - это остается как упражнение для читателя. :) –

+0

Аргумент по умолчанию оценивается только один раз, поэтому он является классом. ОП разъяснил в комментарии к вопросу, что он должен быть на уровне экземпляров. –

+0

@ Series8217: Да, поэтому я задал вопрос - ответ обновился, чтобы показать, как это сделать на уровне экземпляра. –

0

Вам нужен __init__ метод для инициализации элемента данных:

class foo(object): 
    def __init__(self): 
     self.counter = 0 
    def bar(self): 
     self.counter += 1 
     return self.counter 

Установка значений данных непосредственно к функциям является явно не-Pythonic.

+0

Я ценю это, но моя цель - связать переменную счетчика с методом бара (по причинам, указанным в моем вопросе). Я изменил свой ответ на: класса Foo (объект): Защиту __init __ (Я): self.bar.counter = 0 Защиту бара (самость): self.bar.counter + = 1 возвращения self.bar .counter , но теперь я получаю тот же атрибут AttributeError в вызове __init__ – sbell

+0

Не привязывает 'counter' к' bar', как запрошенный OP. –

+0

@ EthanFurman: OP попросил «более питонический» способ сделать это. Присоединение данных непосредственно к функциям противоположно Pythonic. –

0

Если вы хотите, чтобы счетчик был постоянным во всех экземплярах, вы можете сделать следующее, это все еще не Pythonic.

class foo(object): 
    def bar(self): 
     self.bar.im_func.counter += 1 
     return self.bar.im_func.counter 
    bar.counter = 0 
2

К сожалению, чтобы выкопать старую почту, но я наткнулся на нее в моих поисках, и на самом деле найти кого-то с раствором.

Это сообщение в блоге, в котором описывается проблема, с которой вы сталкиваетесь, и как создать декоратор, который делает то, что вы хотите. Вы не только получите необходимую функциональность, но и автор прекрасно справляется с тем, почему Python работает таким образом.

http://metapython.blogspot.com/2010/11/python-instance-methods-how-are-they.html