【问题标题】:How can a function refer stably to itself?函数如何稳定地引用自身?
【发布时间】:2012-08-10 19:26:19
【问题描述】:

[原版的代码乱七八糟。即使在我修复了代码之后,帖子中仍然存在几个非常令人困惑的错别字。我相信我最终也修复了所有这些问题。深表歉意。]

下面对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

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

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

附:这个问题的动机只是为了更好地了解 Python。

【问题讨论】:

  • spam 定义在哪里?
  • 这个例子有点令人困惑 - hamspam 引用 my_own_id()
  • 不错的发现 :-) [您应该编辑将 my_own_id 重命名为垃圾邮件的问题] 我不知道为什么会发生这种情况。如果您再次分配,它会保持稳定:foo = ham; ham(); del ham; foo()(我想这是您问题的答案,尽管没有任何见解:-)
  • 这很有趣:>>> id(my_own_id) 45375408 >>> alias = my_own_id >>> id(alias) 45375408 >>> my_own_id = None >>> id(my_own_id) 505354444 >>> id(alias) 45375408 >>> alias() 505354444
  • @MartijnPieters:我的代码搞砸了;我现在已经修好了。很抱歉造成混乱。

标签: python introspection


【解决方案1】:

似乎引用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

【讨论】:

  • 你真的尝试过吗?我在 python 2.6.1 中得到一个 NameError;在解析参数时,名称 my_own_id 尚不存在。
  • @RussellBorogove 刚刚在 2.6.5 和 2.7 上尝试过,它似乎有效。 ideone.com/EtXTY & ideone.com/bbBSC
  • 啊,我漏掉了my_own_id = None 行。你得到的是None 的id,而不是函数的id。 (我在这里说的是你的第一节,而不是装饰器版本。)
  • @RussellBorogove 哦,我将摆脱该解决方案。我想我实际上本可以得到object() 的 id...
【解决方案2】:

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

更简单、更易于维护的方法是为此编写一个装饰器,它将提供一个包含对函数本身的引用的 nonlocalvariable。

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

【讨论】:

  • 可能想要更改名称,因为按照目前的编写,这并不完全有效:您定义了 own_id 变量,但随后不使用它。
  • 呃,我认为这也行不通......非本地范围仅适用于封闭范围,而不是通过调用者。因为my_own_id 的定义既不在know_thyself 内,也不在new_func 内;没有一个名字是可见的(缺少一些框架检查恶作剧)
【解决方案3】:

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

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

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

【讨论】:

    【解决方案4】:

    装饰器方法可能是最好的一种。这里还有一些好玩的:

    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。这些工作无需对原始功能进行任何修改。

    【讨论】:

      【解决方案5】:
      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>
      

      【讨论】:

      • 很有趣,但这行不通,因为您会看到是否在我发布的测试用例中将me = my_own_id 替换为me = _this_fn()。出于好奇,为什么要在_this_fn 中使用del codedel frame(以及对try...finally 结构的需求)?
      【解决方案6】:

      这是由于范围

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

      【讨论】:

      • 我认为问题不在于为什么,而在于如何规避。
      • @Luke:是的,我知道。我也很好奇。不过,我保留答案以供将来参考。
      【解决方案7】:

      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
      

      这需要您小心,不要使用参数调用函数。

      【讨论】:

      • 这当然只有在重新分配名称之前调用函数时才有效。
      • @Dougal 你可以在定义之后直接调用第一个调用,以确保。
      • 嗯,是的,但如果它是一个昂贵的函数调用,你不一定想这样做。只是指出这并非万无一失。
      猜你喜欢
      • 2021-05-23
      • 1970-01-01
      • 1970-01-01
      • 2019-02-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多