действительно ли возможно получить доступ к атрибутам функционального объекта Python из функционального объема?
например, давайте иметь
def f():
return SOMETHING
f._x = "foo"
f() # -> "foo"
теперь, каково ЧТО-ТО должно быть, если мы хотим иметь содержание атрибута _x возвращенное "нечто"? если это даже возможно (просто)
спасибо
ОБНОВЛЕНИЕ:
я хотел бы следующую работу также:
g = f
del f
g() # -> "foo"
ОБНОВЛЕНИЕ 2:
Оператор, что это не возможно (если это имеет место), и почему, больше удовлетворяет, чем позволение, как фальсифицировать его, например, с другим объектом, чем функция
Заставьте один из аргументов функции по умолчанию быть ссылкой на саму функцию.
def f(self):
return self.x
f.func_defaults = (f,)
Пример использования:
>>> f.x = 17
>>> b = f
>>> del f
>>> b()
17
Оригинальный постер хотел получить решение, которое не требует глобального поиска имен. Простое решение
def f():
return f.x
выполняет поиск глобальной переменной f
при каждом вызове, что не соответствует требованиям. Если f
удаляется, то функция завершается неудачно. Более сложное предложение inspect
терпит неудачу таким же образом.
Что мы хотим, так это выполнить раннее связывание и хранить связанную ссылку внутри самого объекта. Концептуально мы делаем следующее:
def f(self=f):
return self.x
В приведенном выше примере self
является локальной переменной, поэтому глобальный поиск не выполняется. Однако мы не можем написать код как есть, потому что f
еще не определена, когда мы пытаемся привязать к ней значение по умолчанию self
. Вместо этого мы устанавливаем значение по умолчанию после определения f
.
Вот простой декоратор, который сделает это за вас. Обратите внимание, что аргумент self
должен идти последним, в отличие от методов, где self
идет первым. Это также означает, что вы должны указать значение по умолчанию, если любой из ваших других аргументов принимает значение по умолчанию.
def self_reference(f):
f.func_defaults = f.func_defaults[:-1] + (f,)
return f
@self_reference
def foo(verb, adverb='swiftly', self=None):
return '%s %s %s' % (self.subject, verb, adverb)
Example:
>>> foo.subject = 'Fred'
>>> bar = foo
>>> del foo
>>> bar('runs')
'Fred runs swiftly'
Я сомневаюсь, что это лучший способ сделать это, но вы можете получить доступ к атрибутам, используя имя метода внутри метода:
>>> def foo():
... print foo.x
...
>>> foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in foo
AttributeError: 'function' object has no attribute 'x'
>>> foo.x = 5
>>> foo()
5
Ответ довольно прост. Просто используйте тот факт, что имена ищутся во время выполнения, а не во время компиляции:
def f():
return f._x
f._x = "foo"
f() # -> "foo"
Если вы хотите, чтобы она была полностью независимой от имени функции, вам понадобится магия фреймов. Например:
def f2():
import inspect
frame = inspect.currentframe()
fname = frame.f_code.co_name
fobj = frame.f_globals[fname]
print fobj._x
f2._x = 2
f2()
Вы можете просто использовать класс для этого
>>> class F(object):
... def __call__(self, *args, **kw):
... return self._x
...
>>> f=F()
>>> f._x = "foo"
>>> f()
'foo'
>>> g=f
>>> del f
>>> g()
'foo'
Что ж, давайте посмотрим, что это за функция:
>>> 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
Ага! Итак, атрибут был добавлен к объекту функции, но он его не увидит, потому что вместо этого ищет global 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
функции. -object и, как таковой, не имеет доступа к атрибуту 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}
Конечно, вы можете сделать и другие ухищрения, чтобы это выглядело как функция - в частности, трюк с определением класса ... но это не функция как таковая. Все зависит от того, что вам действительно нужно с этим делать.
В качестве обходного пути вы можете использовать фабричную функцию для исправления вашей области:
def factory():
def inner():
print inner.x
return inner
>>> foo=factory()
>>> foo.x=11
>>> foo()
11
>>> bar = foo
>>> del foo
>>> bar()
11
Вот декоратор, который вставляет current_fun в глобальные объекты функций перед выполнением функции. Это неплохой прием, но при этом весьма эффективный.
from functools import wraps
def introspective(f):
@wraps(f)
def wrapper(*args, **kwargs):
exists = 'current_fun' in f.func_globals
old = f.func_globals.get('current_fun',None)
f.func_globals['current_fun'] = wrapper
try:
return f(*args, **kwargs)
finally:
if exists:
f.func_globals['current_fun'] = old
else:
del f.func_globals['current_fun']
return wrapper
@introspective
def f():
print 'func_dict is ',current_fun.func_dict
print '__dict__ is ',current_fun.__dict__
print 'x is ',current_fun.x
Вот пример использования
In [41]: f.x = 'x'
In [42]: f()
func_dict is {'x': 'x'}
__dict__ is {'x': 'x'}
x is x
In [43]: g = f
In [44]: del f
In [45]: g()
func_dict is {'x': 'x'}
__dict__ is {'x': 'x'}
x is x
Здесь используется немного хакерский подход, но, возможно, он пока наиболее правильный, учитывая, что он также работает с вызовом g ()
. Это работает, потому что полагается на любую проверку байт-кода, выполняемую модулем dis в качестве ярлыка.
Это выглядит более хакерским, чем есть на самом деле, отчасти потому, что вызов dis.disassemble ()
выводится на стандартный вывод, поэтому я перенаправляю его в StringIO. Я использую disassemble ()
для его функции выделения последней инструкции (добавьте туда строку print text
, чтобы посмотреть, как она выглядит), и это упрощает получение предыдущей ] LOAD_NAME
и используемую переменную.
Для этого можно было бы использовать более чистую библиотеку проверки байт-кода, вообще не используя модуль dis
, но это доказывает, что это возможно. Возможно, это не самый надежный подход, но, опять же, возможно, он будет работать в большинстве случаев. Я не потратил достаточно времени на изучение внутренней структуры Python или байт-кода, чтобы узнать, предшествуют ли большинству CALL_FUNCTION
байт-кодов инструкций, которые может определить трюк с регулярным выражением.
import inspect
import dis
import re
import sys
import StringIO
def f():
caller = inspect.stack()[1][0]
sys.stdout = StringIO.StringIO()
dis.disassemble(caller.f_code, caller.f_lasti)
text = sys.stdout.getvalue()
sys.stdout = sys.__stdout__
match = re.search(r'LOAD_NAME.*\((.*?)\)\s+-->', text)
name = match.group(1)
try:
func = caller.f_locals[name]
except KeyError:
func = caller.f_globals[name]
return func._x
f._x = 'foo'
print 'call f():', f()
g = f
del f
print 'call g():', g()
Это генерирует следующий вывод:
call f(): foo
call g(): foo