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

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

например. пусть

def f():
    return SOMETHING

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

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

спасибо

UPDATE:

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

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

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

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

Ответ 1

Решение

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

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 является локальной переменной, поэтому глобальный поиск не выполняется. Однако мы не можем написать код as-is, потому что 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)

Пример:

>>> foo.subject = 'Fred'
>>> bar = foo
>>> del foo
>>> bar('runs')
'Fred runs swiftly'

Ответ 2

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

>>> class F(object):
...     def __call__(self, *args, **kw):
...         return self._x
... 
>>> f=F()
>>> f._x = "foo"
>>> f()
'foo'
>>> g=f
>>> del f
>>> g()
'foo'

Ответ 3

Хорошо, посмотрим, какая функция:

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

Dang! Таким образом, в общем случае это невозможно сделать через интроспекцию через кадры выполнения. Проблема заключается в том, что существует разница между функциональным объектом и объектом объекта кода - это то, что выполняется, и это всего лишь один атрибут 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}

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

Ответ 4

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

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


>>> foo=factory()
>>> foo.x=11
>>> foo()
11
>>> bar = foo
>>> del foo
>>> bar()
11

Ответ 5

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

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

Ответ 6

Здесь декоратор, который вводит current_fun в функции globals перед выполнением функции. Это довольно взломанный, но также довольно эффективный.

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

Ответ 7

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

def f():
    return f._x

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

Ответ 8

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

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() 

Ответ 9

Это использует немного хакерский подход, но он, возможно, наиболее корректен до сих пор, учитывая, что он работает с вызовом g(). Он работает, потому что он полагается на то, что проверка байт-кода выполняется dis в качестве ярлыка.

Он выглядит более хакерским, чем на самом деле отчасти потому, что вызов dis.disassemble() печатает на stdout, поэтому я перенаправляю его в 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

Ответ 10

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

как в

class f(object):
        def __new__(cls, x):
            print cls.myattribute
            return x

это работает как в

f.myattribute = "foo"
f(3)
foo
3

то вы можете сделать

g=f
f=None
g(3)
foo
3

Проблема в том, что даже если объект ведет себя как функция, это не так. Следовательно, IDE не могут предоставить вам подпись.

Ответ 11

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

def makeFunc():
    def f():
        return f._x
    return f

Тогда:

>>> f = makeFunc()
>>> f._x = "foo"
>>> f()
'foo'
>>> g = f
>>> del f
>>> g()
'foo'

Ответ 12

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

# closure example of light weight object having class state,
#    local state, and single method
# This is a singleton in the sense that there is a single class
#    state (see Borg singleton pattern notebook)
#    BUT combined with local state
# As long as only one method is needed, this one way to do it
# If a full class singleton object is needed with multiple 
#    methods, best look at one of the singleton patterns

def LW_Object_Factory(localState):

    # class state - doesn't change
    lwof_args = (1, 2, 3)
    lwof_kwargs =  {'a': 4, 'b': 5}

    # local instance - function object - unique per
    # instantiation sharing class state
    def theObj(doc, x):
        print doc, 'instance:'
        print '\tinstance class state:\n\t\targs -', \
              lwof_args, ' kwargs -', lwof_kwargs
        print '\tinstance locals().items():'
        for i in locals().items():
            print '\t\t', i
        print '\tinstance argument x:\n\t\t', '"{}"'.format(x)
        print '\tinstance local state theObj.foo:\n\t\t',\
              '"{}"'.format(theObj.foo)
        print ''

    # setting local state from argument
    theObj.foo = localState

    return(theObj)

lwo1 = LW_Object_Factory('foo in local state for first')
lwo2 = LW_Object_Factory('foo in local state for second')

# prove each instance is unique while sharing class state
print 'lwo1 {} distinct instance from lwo2\n'\
      .format(id(lwo1) <> id(lwo2) and "IS" or "IS NOT")

# run them
lwo1('lwo1', 'argument lwo1') 
lwo2('lwo2', 'argument lwo2')

Ответ 13

Вот стратегия, которая, вероятно, хуже, чем идея func_defaults, но тем не менее интересна. Это взломанно, но я не могу думать о чем-то практически неправильном.

Мы можем реализовать функцию, которая может ссылаться на себя как класс с единственным методом __new__ (метод, который обычно создает новый объект этого класса).

class new:
    """Returns True the first time an argument is passed, else False."""
    seen = set()
    def __new__(cls, x):
        old = x in cls.seen
        cls.seen.add(x)
        return not old

def main():
    print(new(1))  # True
    print(new(2))  # True
    print(new(2))  # false
    is_new = new
    print(is_new(1))  # False

Возможно, этот шаблон может быть полезен для функции регистрации...

class log_once:
    """Log a message if it has not already been logged.

    Args:
        msg: message to be logged
        printer: function to log the message
        id_: the identifier of the msg determines whether the msg
          has already been logged. Defaults to the msg itself.

    This is useful to log a condition that occurs many times in a single
    execution. It may be relevant that the condition was true once, but
    you did not need to know that it was true 10000 times, nor do you
    desire evidence to that effect to fill your terminal screen.
    """
    seen = set()
    def __new__(cls, msg, printer=print, id_=None):
        id_ = id_ or msg
        if id_ not in cls.seen:
            cls.seen.add(id_)
            printer(id_)


if __name__ == '__main__':
    log_once(1)
    log_once(1)
    log_once(2)

Ответ 14

Просто определите свою функцию внутри замыкания:

def generate_f():
    def f():
        return f.x
    return f

f = generate_f()

f.x = 314
g = f

del f
print g()
# => 314

Ответ 15

Мне нравится это много.

from functools import update_wrapper

def dictAsGlobals(f):
    nf = type(f)(f.__code__, f.__dict__, f.__name__, f.__defaults__, f.__closure__)
    try: nf.__kwdefaults__ = f.__kwdefaults__
    except AttributeError: pass
    nf.__dict__ = f.__dict__
    nf.__builtins__ = f.__globals__["__builtins__"]
    return update_wrapper(nf, f)

@dictAsGlobals
def f():
    global timesCalled
    timesCalled += 1
    print(len.__doc__.split("\n")[0])
    return factor0 * factor1

vars(f).update(timesCalled = 0, factor0 = 3, factor1 = 2)

print(f())
print(f())
print(f.timesCalled)