我想要一个函数来引用它自己。例如是递归的。
所以我做了类似的事情:
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
的名称不存在;有没有解决方法?
我没有真正可能发生的实际用例;我只是出于好奇而问。
答案 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仍然是超级实验性的,但我认为它非常整洁。