一个函数如何能够稳定地引用自身?

时间:2012-08-10 19:26:19

标签: python introspection

[原始版本中的代码严重搞砸了。即使在我修改了代码之后,仍有几个高度混乱的错别字留在帖子中。我相信我终于修复了所有这些。深表歉意。]

下面对alias的两次调用产生不同的输出,因为与变量my_own_id相关联的对象在两次调用之间发生变化:

>>> def my_own_id():
...     me = my_own_id
...     return id(me)
... 
>>> alias = my_own_id
>>> alias()
4301701560
>>> my_own_id = None
>>> alias()
4296513024

我可以在me的定义中为my_own_id分配什么,以便在后续重新定义my_own_id变量时其输出保持不变? (IOW,以便内部me变量总是引用相同的函数对象?)

(我可以获取当前帧(带inspect.currentframe()),但它只包含对当前代码对象的引用,而不包含对当前函数的引用。)

P.S。这个问题的动机只是为了更好地了解Python。

7 个答案:

答案 0 :(得分:3)

似乎引用my_own_id将在全局命名空间字典中查找'my_own_id',因此它将始终是函数定义中使用的名称。由于该名称可以分配给不同的值,因此检索的值也可以更改。如果使me成为默认参数,则可以在函数定义中将其分配给函数本身,以保持对实际函数的引用。

您可以使用此装饰器隐式传递原始函数本身作为第一个参数。

>>> from functools import wraps
>>> def save_id(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            return func(func, *args, **kwargs)
        return wrapper


>>> @save_id
def my_own_id(me): # me is passed implicitly by save_id
    return id(me)

>>> alias = my_own_id
>>> alias()
40775152
>>> my_own_id = 'foo'
>>> alias()
40775152

答案 1 :(得分:2)

实际上,如果仅依赖函数名称,如果该名称在全局变量空间中被覆盖(在模块中定义了函数),则使用函数名称的引用将失败

更简单,更有效的方法是为它编写一个装饰器,它将提供一个包含对函数本身的引用的nonlocal变量。

from functools import wraps

def know_thyself(func):
   @wraps(func):
   def new_func(*args, **kwargs):
        my_own_id = func
        return func(*args, **kwargs)
   return new_func

可以用作:

>>> @know_thyself
... def my_own_id():
...     me = my_own_id
...     return id(me)
... 

使用框架内省还有另一种可能的方法,远非如此干净 并重新使用相同的目标代码重建新函数。我在这篇文章中使用了这个 关于Python中的自引用lambda表达式: http://metapython.blogspot.com.br/2010/11/recursive-lambda-functions.html

答案 2 :(得分:1)

好吧,如果你不介意调用一个函数(将所需的函数放入全局范围),你可以包装函数来保护它的定义:

>>> def be_known():
...     global my_own_id
...     def _my_own_id():
...         return id(_my_own_id)
...     my_own_id = _my_own_id
... 
>>> be_known()
>>> my_own_id()
140685505972568
>>> alias, my_own_id = my_own_id, None
>>> alias()
140685505972568

请注意,受保护的函数必须使用非本地名称而不是全局名称来调用自身。

答案 3 :(得分:1)

装饰者的方法可能是最好的方法。以下是一些有趣的内容:

  1. 劫持其中一个函数参数以提供静态变量。

    def fn(fnid=None):
        print "My id:", fnid
    fn.func_defaults = (id(fn),)
    
  2. 有几种方法可以在这里获取当前函数:Python code to get current function into a variable?;其中大部分涉及在各种地方搜索currentframe.f_code。这些工作不需要对原始功能进行任何修改。

答案 4 :(得分:1)

import inspect
def _this_fn():
    try:
        frame = inspect.currentframe().f_back
        code = frame.f_code
        return frame.f_globals[code.co_name]
    finally:
        del code
        del frame

def myfunc(*parms):
    print _this_fn()

>>> myfunc(1)
<function myfunc at 0x036265F0>
>>> myfunc
<function myfunc at 0x036265F0>

答案 5 :(得分:0)

这是由于范围

>>> def foo():
...     x = foo
...     print x
... 
>>> foo()
<function foo at 0x10836e938>
>>> alias = foo
>>> alias()
<function foo at 0x10836e938>
>>> foo = None
>>> alias()
None
>>> foo = []
>>> alias()
[]
>>> del foo
>>> alias()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in foo
NameError: global name 'foo' is not defined
>>> 

答案 6 :(得分:0)

Luke有一个想法,但似乎没有开发它:使用一个可变的默认参数来保存函数对象中的值。默认参数值仅在定义函数时计算一次,并在此之后保留其先前的值。

>>> def my_own_id(me=[None]):
    if not me[0]:
        me[0] = my_own_id
    return id(me[0])

>>> alias = my_own_id
>>> alias()
40330928
>>> my_own_id = None
>>> alias()
40330928

您需要注意不要使用参数调用该函数。