Хорошо, давайте посмотрим на то, что функция:
>>> def foo():
... return x
...
>>> foo.x = 777
>>> foo.x
777
>>> foo()
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
File "<interactive input>", line 2, in foo
NameError: global name 'x' is not defined
>>> dir(foo)
['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__get__',
'__getattribute__', '__hash__', '__init__', '__module__', '__name__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__',
'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc',
'func_globals', 'func_name', 'x']
>>> getattr(foo, 'x')
777
Ага!Таким образом, атрибут был добавлен к объекту функции, но он не увидит его, потому что вместо этого ищет глобальный x
.
Мы можем попытаться получить кадр выполнения функции и попытаться посмотреть, что там есть.(по сути то, что предложил Энтони Конг, но без inspect
модуля):
>>> def foo():
... import sys
... return sys._getframe()
...
>>> fr = foo()
>>> dir(fr)
['__class__', '__delattr__', '__doc__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', 'f_back', 'f_builtins', 'f_code', 'f_exc_traceback', 'f_exc_type', 'f_exc_value', 'f_globals', 'f_lasti', 'f_lineno', 'f_locals', 'f_restricted', 'f_trace']
>>> fr.f_locals
{'sys': <module 'sys' (built-in)>}
>>> fr.f_code
<code object foo at 01753020, file "<interactive input>", line 1>
>>> fr.f_code.co_code
'd\x01\x00d\x00\x00k\x00\x00}\x00\x00|\x00\x00i\x01\x00\x83\x00\x00S'
>>> fr.f_code.co_name
'foo'
Ага!Поэтому, может быть, мы можем получить имя функции из имени блока кода, а затем посмотреть в обход атрибута?Конечно же:
>>> getattr(fr.f_globals[fr.f_code.co_name], 'x')
777
>>> fr.f_globals[fr.f_code.co_name].x
777
>>> def foo():
... import sys
... frm = sys._getframe()
... return frm.f_globals[frm.f_code.co_name].x
...
>>> foo.x=777
>>> foo()
777
Отлично!Но выдержит ли переименование и удаление оригинальной функции?
>>> g = foo
>>> g.func_name
'foo'
>>> g.func_code.co_name
'foo'
Ах, очень сомнительно.Объект функции и его объект кода все еще настаивают на том, что они называются foo
.Конечно же, вот где он ломается:
>>> g.x
777
>>> g.x=888
>>> foo.x
888
>>> g()
888
>>> del foo
>>> g()
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
File "<interactive input>", line 4, in foo
KeyError: 'foo'
Черт!Так что в общем случае это невозможно сделать с помощью самоанализа через фреймы выполнения.Похоже, проблемы заключаются в том, что существует разница между объектом функции и объектом кода - объекты кода - это то, что выполняется, и является всего лишь одним атрибутом func_code
объекта-функции и кактакой не имеет доступа к атрибуту func_dict
, где наш атрибут x
:
>>> g
<function foo at 0x0173AE30>
>>> type(g)
<type 'function'>
>>> g.func_code
<code object foo at 017532F0, file "<interactive input>", line 1>
>>> type(g.func_code)
<type 'code'>
>>> g.func_dict
{'x': 888}
Конечно, вы можете использовать другие элементы chicanery, чтобы они выглядели как функции - в частности, трюк с классомопределение ... но это не функция как таковая.Все зависит от того, что вам действительно нужно с этим делать.