как доступ из Python функции свои атрибуты?

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

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

def f():
    return SOMETHING

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

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

спасибо

обновление:

Я хотел бы также выполнить следующую работу:

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

обновление 2:

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

15 ответов


решение

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

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 предложение терпит неудачу таким же образом.

что мы хотим выступить раннее связывание и сохраните связанную ссылку внутри самого объекта. Концептуально мы делаем следующее:--18-->

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)

пример:

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

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

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

мы можем попытаться захватить фрейм выполнения функции и попытаться посмотреть ,что там (по сути, то, что предложил Энтони Конг, но без 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}

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


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

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


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

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

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

вот декоратор, который вводит 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

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

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

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

это выглядит более хакерским, чем это на самом деле отчасти потому, что 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

как насчет использования класса вместо функции и злоупотребления __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

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


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

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

затем:

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

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

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

вот стратегия, которая, вероятно, хуже, чем 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)

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

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

f = generate_f()

f.x = 314
g = f

del f
print g()
# => 314

мне это очень нравится.

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)