Как функция может получить доступ к своим собственным атрибутам?

действительно ли возможно получить доступ к атрибутам функционального объекта Python из функционального объема?

например, давайте иметь

def f():
    return SOMETHING

f._x = "foo"
f()           # -> "foo"

теперь, каково ЧТО-ТО должно быть, если мы хотим иметь содержание атрибута _x возвращенное "нечто"? если это даже возможно (просто)

спасибо

ОБНОВЛЕНИЕ:

я хотел бы следующую работу также:

g = f
del f
g()          # -> "foo"

ОБНОВЛЕНИЕ 2:

Оператор, что это не возможно (если это имеет место), и почему, больше удовлетворяет, чем позволение, как фальсифицировать его, например, с другим объектом, чем функция

39
задан martineau 3 December 2018 в 18:20
поделиться

9 ответов

Решение

Заставьте один из аргументов функции по умолчанию быть ссылкой на саму функцию.

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'
48
ответ дан 27 November 2019 в 02:13
поделиться

Я сомневаюсь, что это лучший способ сделать это, но вы можете получить доступ к атрибутам, используя имя метода внутри метода:

>>> 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
3
ответ дан 27 November 2019 в 02:13
поделиться

Ответ довольно прост. Просто используйте тот факт, что имена ищутся во время выполнения, а не во время компиляции:

def f():
    return f._x

f._x = "foo"
f()           # -> "foo"
2
ответ дан 27 November 2019 в 02:13
поделиться

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

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() 
1
ответ дан 27 November 2019 в 02:13
поделиться

Вы можете просто использовать класс для этого

>>> class F(object):
...     def __call__(self, *args, **kw):
...         return self._x
... 
>>> f=F()
>>> f._x = "foo"
>>> f()
'foo'
>>> g=f
>>> del f
>>> g()
'foo'
24
ответ дан 27 November 2019 в 02:13
поделиться

Что ж, давайте посмотрим, что это за функция:

>>> 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}

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

15
ответ дан 27 November 2019 в 02:13
поделиться

В качестве обходного пути вы можете использовать фабричную функцию для исправления вашей области:

def factory():
    def inner():
        print inner.x
    return inner


>>> foo=factory()
>>> foo.x=11
>>> foo()
11
>>> bar = foo
>>> del foo
>>> bar()
11
8
ответ дан 27 November 2019 в 02:13
поделиться

Вот декоратор, который вставляет 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
3
ответ дан 27 November 2019 в 02:13
поделиться

Здесь используется немного хакерский подход, но, возможно, он пока наиболее правильный, учитывая, что он также работает с вызовом 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
1
ответ дан 27 November 2019 в 02:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: