函数如何访问自己的属性?

时间:2010-06-24 10:55:04

标签: python function scope closures attributes

是否可以从函数范围内访问python函数对象属性?

e.g。我们有

def f():
    return SOMETHING

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

现在,如果我们想让_x属性内容“foo”返回,那么SOMETHING必须是什么?如果它甚至可能(简单地)

感谢

更新

我也想做以下工作:

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

更新2:

声明不可能(如果是这种情况),以及为什么比提供如何伪造它的方式更令人满意,例如使用与函数不同的对象

16 个答案:

答案 0 :(得分:47)

解决方案

使函数的默认参数之一成为对函数本身的引用。

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)

示例:

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

答案 1 :(得分:24)

您可以使用一个类来执行此操作

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

答案 2 :(得分:15)

好吧,让我们来看看功能是什么:

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

我们可以尝试抓住函数执行的框架并尝试查看其中的内容(基本上是Anthony Kong所建议但没有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}

当然有其他的chicanery你可以这样做,它似乎是函数 - 特别是类定义的技巧......但这本身不是一个函数。这完全取决于你真正需要做些什么。

答案 3 :(得分:8)

作为一种解决方法,您可以使用工厂函数来修复范围:

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


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

答案 4 :(得分:3)

我怀疑这是实现此目的的最佳方法,但您可以使用方法中的方法名称来访问属性:

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

答案 5 :(得分:3)

这是一个装饰器,它在执行函数之前将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

答案 6 :(得分:2)

答案很简单。只需使用在执行时查找的事实名称,而不是编译时间:

def f():
    return f._x

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

答案 7 :(得分:1)

如果您希望它完全独立于函数名称,则需要一些框架魔法。例如:

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

答案 8 :(得分:1)

这使用了一些hackish方法,但到目前为止它可能是最正确的,因为它也适用于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

答案 9 :(得分:0)

如何使用类而不是函数并滥用__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无法为您提供签名。

答案 10 :(得分:0)

另一种实现此目的的方法是在另一个函数中定义函数,并使外部函数返回内部函数。然后内部函数可以通过闭包访问自己。这是一个简单的例子:

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

然后:

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

答案 11 :(得分:0)

如果只需要一个方法但你想要一个具有共享类状态和单个实例状态的轻量级类,你可以尝试这样的闭包模式:

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

答案 12 :(得分:0)

这是一个可能比func_defaults想法更糟糕的策略,但仍然很有趣。这很hacky但我无法想到任何实际上错误的东西。

我们可以实现一个函数,它可以将自己称为具有单个__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)

答案 13 :(得分:0)

只需在闭包中定义你的函数:

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

f = generate_f()

f.x = 314
g = f

del f
print g()
# => 314

答案 14 :(得分:0)

我很喜欢这个。

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)

答案 15 :(得分:0)

很抱歉收到的答复很晚,但我偶然发现了这一点。我不得不争辩说,要求“ g”起作用的方式不是Python风格的。在函数内部,名称“ f”是指调用函数时全局变量的值。鉴于此,请考虑以下事项:

def f():
    print(f)
f, g = 42, f
g()  # prints 42
del f
g()  # raises an exception 

希望没有人认为这是不正确的行为。鉴于这一事实,我只对需要在函数内部使用不同的变量名(例如“ self”)的答案投票。