Установка атрибутов в методе после создания класса вызывает "объект instancemethod не имеет атрибута", но атрибут явно присутствует

В Python (2 и 3) мы можем назначить атрибуты функции:

>>> class A(object):
...     def foo(self):
...         """ This is obviously just an example """
...         return "FOO{}!!".format(self.foo.bar)
...     foo.bar = 123
...
>>> a = A()
>>> a.foo()
'FOO123!!'

И это круто.

Но почему мы не можем изменить foo.bar позже? Например, в конструкторе, вот так:

>>> class A(object):
...     def __init__(self, *args, **kwargs):
...         super(A, self).__init__(*args, **kwargs)
...         print(self.foo.bar)
...         self.foo.bar = 456  # KABOOM!
...     def foo(self):
...         """ This is obviously just an example """
...         return "FOO{}!!".format(self.foo.bar)
...     foo.bar = 123
...
>>> a = A()
123
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in __init__
AttributeError: 'instancemethod' object has no attribute 'bar'

Python претензий нет bar хотя он напечатал это хорошо только на предыдущей строке.

Та же самая ошибка происходит, если мы пытаемся изменить это непосредственно на классе:

>>> A.foo.bar
123
>>> A.foo.bar = 345  # KABOOM!
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'instancemethod' object has no attribute 'bar'

Что здесь происходит, то есть почему мы видим это поведение?

Есть ли способ установить атрибуты функции после создания класса?

(Мне известно о нескольких альтернативах, но я явно задаюсь вопросом об атрибутах методов здесь или, возможно, более широкой проблеме.)


Мотивация: Django использует возможность устанавливать атрибуты на методы, например:

class MyModelAdmin(ModelAdmin):
    ...

    def custom_admin_column(self, obj):
        return obj.something()
    custom_admin_column.admin_order_field ='relation__field__span'
    custom_admin_column.allow_tags = True

1 ответ

Решение

Настройка foo.bar внутри тела класса работает, потому что foo является фактическим foo функция. Тем не менее, когда вы делаете

self.foo.bar = 456

self.foo это не та функция. self.foo является объектом метода экземпляра, созданным по требованию при обращении к нему. Вы не можете установить атрибуты для этого по нескольким причинам:

  1. Если эти атрибуты хранятся в foo функции, а затем присваивая a.foo.bar оказывает неожиданное влияние на b.foo.barвопреки всем обычным ожиданиям по поводу присвоения атрибутов.
  2. Если эти атрибуты хранятся в self.foo объект экземпляра метода, они не будут отображаться при следующем обращении self.fooпотому что вы получите новый объект метода экземпляра в следующий раз.
  3. Если эти атрибуты хранятся в self.foo объект экземпляра метода, и вы измените правила так self.foo это всегда один и тот же объект, то это массово раздувает каждый объект в Python, чтобы хранить кучу объектов метода экземпляра, которые вам почти никогда не нужны.
  4. Если эти атрибуты хранятся в self.__dict__что насчет объектов, которые не имеют __dict__? Кроме того, вам нужно придумать какое-то правило искажения имен или хранить нестроковые ключи в self.__dict__У обоих есть свои проблемы.

Если вы хотите установить атрибуты на foo Функция после определения класса, вы можете сделать это с A.__dict__['foo'].bar = 456, (Я использовал A.__dict__ обойти вопрос о том, A.foo это функция или объект несвязанного метода, который зависит от вашей версии Python. Если A наследуется fooвам придется либо разобраться с этой проблемой, либо получить доступ к классу наследуемого им класса foo от.)

Другие вопросы по тегам