是否可以从函数范围内访问python函数对象属性?
e.g。我们有
def f():
return SOMETHING
f._x = "foo"
f() # -> "foo"
现在,如果我们想让_x属性内容“foo”返回,那么SOMETHING必须是什么?如果它甚至可能(简单地)
感谢
更新
我也想做以下工作:
g = f
del f
g() # -> "foo"
更新2:
声明不可能(如果是这种情况),以及为什么比提供如何伪造它的方式更令人满意,例如使用与函数不同的对象
答案 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”)的答案投票。