有没有办法在python中引用当前函数?

时间:2013-05-29 19:02:43

标签: python

我想要一个函数来引用它自己。例如是递归的。

所以我做了类似的事情:

def fib(n):
    return n if n <= 1 else fib(n-1)+fib(n-2)

大部分时间这都很好,但fib实际上并不是指自己;它指的是封闭块中fib的绑定。因此,如果由于某种原因fib被重新分配,它将会中断:

>>> foo = fib
>>> fib = foo(10)
>>> x = foo(8)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in fib
TypeError: 'int' object is not callable

如果可能的话,如何防止这种情况发生(来自fib内部)?据我所知,在完全执行函数定义之前,fib的名称不存在;有没有解决方法?

我没有真正可能发生的实际用例;我只是出于好奇而问。

4 个答案:

答案 0 :(得分:6)

我会为这个

做装饰
from functools import wraps

def selfcaller(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(wrapper, *args, **kwargs)
    return wrapper

并像

一样使用它
@selfcaller
def fib(self, n):
    return n if n <= 1 else self(n-1)+self(n-2)

这实际上是一种定义Fixed Point Combinator(或Y组合器)的可读方式:

fix = lambda g: (lambda f: g(lambda arg: f(f)(arg))) (lambda f: g(lambda arg: f(f)(arg)))

用法:

fib = fix(lambda self: lambda n: n if n <= 1 else self(n-1)+self(n-2))

或:

@fix
def fib(self):
    return lambda n: n if n <= 1 else self(n-1)+self(n-2)

此处的绑定发生在形式参数中,因此不会出现问题。

答案 1 :(得分:3)

没有办法做你想做的事。你是正确的,fib在执行函数定义之前不存在(或者更糟糕的是,它存在但指的是完全不同的东西......),这意味着{{1}内部没有解决方法 } 可能有效。*

但是,如果您愿意放弃该要求,则的解决方法有效。例如:

fib

现在def _fibmaker(): def fib(n): return n if n <= 1 else fib(n-1)+fib(n-2) return fib fib = _fibmaker() del _fibmaker 指的是来自本地环境的fib调用中的绑定。当然,即使可以替换,如果你真的想要,但它并不容易(_fibmaker属性不可写;它是一个元组,所以你不能替换它的任何单元格;每个单元格的fib.__closure__都是一个只读属性,...),你不可能偶然做到这一点。

还有其他方法可以做到这一点(例如,在cell_contents中使用一个特殊的占位符,并使用装饰函数替换占位符的装饰器),它们都是同样不明显和丑陋的,这可能是似乎违反了TOOWTDI。但在这种情况下,“它”是你可能不想做的事情,所以它并不重要。


这里有一种方法可以为使用fib而不是自己的名称的函数编写一般的纯python装饰器,而不需要函数的额外self参数:

self

当然,这对于具有从def selfcaller(func): env = {} newfunc = types.FunctionType(func.__code__, globals=env) env['self'] = newfunc return newfunc @selfcaller def fib(n): return n if n <= 1 else self(n-1)+self(n-2) 绑定的任何自由变量的函数不起作用,但您可以通过一些内省来解决这个问题。而且,虽然我们正在使用它,但我们无需在函数定义中使用globals

self

这是Python 3.x特有的;一些属性名称在2.x中是不同的,但是否则它们是相同的。

这仍然不是100%完全一般。例如,如果您希望能够在方法上使用它,那么即使类或对象重新定义了它们的名称,它们仍然可以调用自己,您需要稍微不同的技巧。并且有一些病态案例可能需要从def selfcaller(func): env = dict(func.__globals__) newfunc = types.FunctionType(func.__code__, globals=env) env[func.__code__.co_name] = newfunc return newfunc 中构建新的CodeType。但基本思路是一样的。


*就Python而言,在绑定名称之前,它不存在......但很明显,在封面下,解释器必须知道您定义的函数的名称。并且至少有一些口译员提供了不可移植的方式来获取这些信息。

例如,在CPython 3.x中,您可以非常轻松地获取当前正在定义的函数的名称 - 它只是func.__code__.co_code

当然,这对你没有任何好处,因为没有任何东西(或错误的东西)与这个名字绑定。但请注意那里的sys._getframe().f_code.co_name。那是当前帧的代码对象。当然你不能直接调用代码对象,但是你可以通过生成一个新函数或者使用bytecodehacks来间接调用代码对象。

例如:

f_code

同样,这不会处理每个病态的情况......但我能想到的唯一方法是实际保持对框架的循环引用,或者至少是它的全局变量(例如,通过传递{{1} }),这似乎是一个非常糟糕的主意。

请参阅Frame Hacks了解更多聪明的事情。


最后,如果你愿意完全退出Python,你可以创建一个导入钩子来预处理或编译你的代码,从Python自定义扩展,比如def fib2(n): f = sys._getframe() fib2 = types.FunctionType(f.f_code, globals=globals()) return n if n<=1 else fib2(n-1)+fib2(n-2) 到纯Python和/或字节码。

如果你正在考虑“但这听起来像一个宏会比预处理器黑客更好,如果只有Python有宏”......那么你可能更喜欢使用一个预处理器黑客来提供Python宏,如MacroPy,然后将扩展名编写为宏。

答案 2 :(得分:2)

就像abamert所说的那样“......内心无法解决问题......”。

这是我的方法:

def fib(n):
    def fib(n):
        return n if n <= 1 else fib(n-1)+fib(n-2)
    return fib(n)

答案 3 :(得分:1)

有人问我基于宏的解决方案,所以这里是:

# macropy/my_macro.py
from macropy.core.macros import *

macros = Macros()

@macros.decorator()
def recursive(tree, **kw):
    tree.decorator_list = []

    wrapper = FunctionDef(
        name=tree.name,
        args=tree.args,
        body=[],
        decorator_list=tree.decorator_list
    )

    return_call = Return(
        Call(
            func = Name(id=tree.name),
            args = tree.args.args,
            keywords = [],
            starargs = tree.args.vararg,
            kwargs = tree.args.kwarg
        )
    )

    return_call = parse_stmt(unparse_ast(return_call))[0]

    wrapper.body = [tree, return_call]

    return wrapper

可以使用如下:

>>> import macropy.core.console
0=[]=====> MacroPy Enabled <=====[]=0
>>> from macropy.my_macro import macros, recursive
>>> @recursive
... def fib(n):
...     return n if n <= 1 else fib(n-1)+fib(n-2)
...
>>> foo = fib
>>> fib = foo(10)
>>> x = foo(8)
>>> x
21

它基本上完成了hus787给出的包装:

  • 创建一个return fib(...)的新语句,该语句使用原始函数的参数列表作为...
  • 创建一个新的def,名称相同,args相同,decorator_list与旧的相同
  • 将新函数放在新函数的主体中,然后将return语句放在一起
  • 剥去其装饰器的原始功能(我假设你想装饰包装器)

parse_stmt(unparse_ast(return_call))[0]垃圾快速破解工作(你实际上不能只从函数的参数列表中复制argument AST并在{{1}中使用它们AST)但这只是细节。

为了表明它实际上是这样做的,你可以添加一个Call语句来查看转换函数的样子:

print unparse_ast

,如上所述,打印

@macros.decorator()
def recursive(tree, **kw):
    ...
    print unparse_ast(wrapper)
    return wrapper

看起来正是你想要的!它适用于任何函数,有多个args,kwargs,defaults等,但我懒得测试。使用AST有点冗长,MacroPy仍然是超级实验性的,但我认为它非常整洁。