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

有可能从函数范围内访问python函数对象的属性?

例如,让我们有

def f(): return SOMETHING f._x = "foo" f() # -> "foo" 

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

谢谢

更新:

我也想下面的工作:

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

更新2:

声明这是不可能的(如果是这种情况),以及为什么比提供一种方式更令人满意,比如用一个不同的对象而不是一个函数

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

 def f(self): return self.x f.func_defaults = (f,) 

用法示例:

 >>> fx = 17 >>> b = f >>> del f >>> b() 17 

说明

原始的海报想要一个不需要全局名称查找的解决scheme。 简单的解决scheme

 def f(): return fx 

在每个呼叫上执行查找全局variablesf ,这不符合要求。 如果f被删除,则该function失败。 更复杂的inspectbuild议以同样的方式失败。

我们想要的是执行早期绑定并将绑定引用存储在对象本身内。 以下是概念上我们正在做的:

 def f(self=f): return self.x 

在上面, self是一个局部variables,所以不执行全局查找。 但是,我们不能按原样编写代码,因为当我们试图将self的默认值绑定到它时, f还没有定义。 相反,我们在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' 

那么,让我们看看是什么function:

 >>> 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代替。

我们可以尝试抓住函数执行的框架,并试图看看那里(基本上是孔安东build议的,但没有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 

那很棒! 但是,它会忍受原有function的重命名和删除吗?

 >>> g = foo >>> g.func_name 'foo' >>> g.func_code.co_name 'foo' 

啊,非常可疑。 函数对象及其代码对象仍然坚持称为foo 。 果然,这里是它的破碎点:

 >>> gx 777 >>> gx=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} 

当然,你也可以做其他的事情,这样看起来就像function – 特别是类定义的诀窍……但这本身并不是一个function。 这一切都取决于你真的需要做什么。

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

 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 

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

 def f(): return f._x f._x = "foo" f() # -> "foo" 

这是一个装饰器,在执行函数之前将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]: fx = '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 f2(): import inspect frame = inspect.currentframe() fname = frame.f_code.co_name fobj = frame.f_globals[fname] print fobj._x f2._x = 2 f2() 

这使用了一些hackish的方法,但它可能是迄今为止最正确的,因为它也适用于g()调用。 它的工作原理是依赖于dis模块执行的任何字节码检查,作为快捷方式。

它看起来比实际上更dis.disassemble()因为dis.disassemble()调用打印到标准输出,所以我把它redirect到一个StringIO。 我使用disassemble()来突出显示最后一条指令(在其中添加一条print text行以查看它的外观),这样可以更轻松地抓取之前的LOAD_NAME及其使用的variables。

可以使用一个更清洁的字节码检查库来完成这个工作,而不需要使用dis模块,但是这certificate了这是可能的。 这可能不是最稳健的方法,但是也可能在大多数情况下都能正常工作。 我没有花费足够的时间在Python内部或字节码中查询,知道大多数CALL_FUNCTION字节码是否立即被正则expression式技巧挑出的指令所引导。

 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 

问题是,即使对象的行为像一个function,它不是。 因此,IDE无法为您提供签名。

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

 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想法更差的策略,但仍然很有趣。 这是hacky,但我想不出有什么实际上错误的。

我们可以用一个__new__方法(通常创build该类的新对象的方法)实现一个可以引用自己作为类的函数。

 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 

也许这个模式可能对日志function有用…

 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) 

只需在closures中定义你的函数:

 def generate_f(): def f(): return fx return f f = generate_f() fx = 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)