Следующий код
class Foo:
def bar(self) -> None:
pass
foo = Foo()
if hasattr(foo.bar, '__annotations__'):
foo.bar.__annotations__ = 'hi'
сбой
AttributeError: 'method' object has no attribute '__annotations__'
Как это может произойти?
Всего 2 ответа
Ошибка атрибута здесь возникает потому, что вы не можете установить атрибут объекта метода:
>>> foo.bar.baz = 42
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'method' object has no attribute 'baz'
Исключение здесь, возможно, запутывает, потому что объекты method
обертывают объект функции и атрибут прокси-сервера для доступа к этому базовому функциональному объекту. Поэтому, когда атрибуты функции существуют, то hasattr()
в методе вернет True
:
>>> hasattr(foo.bar, 'baz')
False
>>> foo.bar.__func__.baz = 42
>>> hasattr(foo.bar, 'baz')
True
>>> foo.bar.baz
42
Однако вы все равно не можете установить эти атрибуты с помощью метода, независимо от того,
>>> hasattr(foo.bar, 'baz')
True
>>> foo.bar.baz = 42
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'method' object has no attribute 'baz'
Таким образом, только потому, что атрибут может быть прочитан, это не значит, что вы можете его установить . hasattr()
говорит правду, вы просто интерпретируете ее как нечто иное.
Теперь, если вы попытались установить атрибут __annotations__
непосредственно на базовый объект функции, вы получите другое сообщение об ошибке:
>>> foo.bar.__func__.__annotations__ = 'hi'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __annotations__ must be set to a dict object
Вы бы хотели использовать объект словаря здесь:
>>> foo.bar.__func__.__annotations__ = {'return': 'hi'}
>>> foo.bar.__annotations__
{'return': 'hi'}
Однако, поскольку __annotations__
является изменчивым словарем, проще просто манипулировать ключами и значениями для этого объекта, что вполне возможно сделать через оболочку метода:
>>> foo.bar.__annotations__['return'] = 'int'
>>> foo.bar.__annotations__
{'return': 'int'}
Теперь, если вы надеялись установить аннотации для каждого экземпляра , вы не можете уйти с настройкой атрибутов объектов метода, потому что объекты метода являются эфемерными , они создаются только для вызова, а затем обычно отбрасываются сразу после.
Вам нужно будет использовать объекты дескриптора метода через метакласс и повторно создавать атрибут __annotations__
для каждого раза, или вместо этого вы можете предварительно связать методы с новым функциональным объектом, которому будут предоставлены свои собственные атрибуты. Затем вам придется заплатить большую цену памяти:
import functools
foo.bar = lambda *args, **kwargs: Foo.bar(foo, *args, **kwargs)
functools.update_wrapper(foo.bar, Foo.bar) # copy everything over to the new wrapper
foo.bar.__annotations__['return'] = 'hi'
В любом случае вы полностью уничтожаете важные оптимизации скорости, сделанные в Python 3.7 таким образом.
И инструменты, которые работают в наиболее важном случае использования __annatotions__
, типа подсказки, на самом деле не выполняют код, они читают код статически и полностью пропускают эти изменения во время выполнения.
Вы получаете сообщение об ошибке. потому что __annotations__
является словарем. Если вы хотите изменить значения, вам нужно будет сделать это следующим образом:
if hasattr(foo.bar, '__annotations__'):
foo.bar.__annotations__['return'] = 'hi'
Это сделает возвращаемое значение вашего foo.bar
be hi
вместо None
. Единственное, что я не уверен в том, как __annotations__
, не позволяя вам изменять их из dict в строку, но я полагаю, что это некоторая внутренняя проверка в источнике.
ОБНОВИТЬ
Для большего контроля над сигнатурой вы можете использовать модуль inspect
и получить объект Signature своего класса (или метода) и отредактировать его оттуда. Например
import inspect
sig = inspect.signature(foo.bar)
sig.return_annotation # prints None (before modifying)
sig.replace(return_annotation="anything you want")
Подробнее об этом здесь