你想做的事情是无法实现的。你说得对,fib
在函数定义执行之前不存在(或者更糟糕的是,它存在但指向完全不同的东西...),这意味着从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
是一个只读属性,...),你不可能无意间这样做。
还有其他方法可以做到这一点(例如,在
fib
中使用一个特殊的占位符,并使用装饰器将占位符替换为装饰的函数),它们都同样不明显且丑陋,这似乎违反了TOOWTDI的原则。但在这种情况下,“it”是您可能不想进行的操作,因此并不重要。
以下是一种能够编写通用纯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”的需要。
def selfcaller(func):
env = dict(func.__globals__)
newfunc = types.FunctionType(func.__code__, globals=env)
env[func.__code__.co_name] = newfunc
return newfunc
这是特定于Python 3.x的;在2.x中,一些属性名称可能不同,但其他方面是相同的。
这仍然不是100%完全通用的。例如,如果您想要能够将其用于方法,以便它们可以调用自己,即使类或对象重新定义了它们的名称,您需要使用稍微不同的技巧。还有一些病态情况可能需要构建一个新的CodeType
,其中包含func.__code__.co_code
。但基本思想是相同的。
* 就Python而言,在名称绑定之前,它并不存在...但显然,在内部,解释器必须知道您正在定义的函数的名称。至少一些解释器提供了非可移植的方法来获取该信息。
例如,在CPython 3.x中,您可以非常容易地获取当前正在定义的函数的名称-只需使用sys._getframe().f_code.co_name
即可。
当然,这对您没有任何直接好处,因为没有任何内容(或错误的内容)与该名称绑定。但请注意其中的f_code
。那是当前帧的代码对象。当然,您无法直接调用代码对象,但是您可以间接地这样做,通过生成一个新的函数或使用bytecodehacks
。
例如:
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)
再次说明,这种方法不能处理所有的病态情况...但我能想到的唯一解决方法是实际上保留对帧的循环引用,或至少保留其全局变量(例如,通过传递 globals = f.f_globals
),但这似乎是一个非常糟糕的主意。
请参见Frame Hacks以了解更多聪明的技巧。
最后,如果你愿意完全离开Python,你可以创建一个导入hook,它会预处理或编译你的代码,从Python自定义扩展中转换为纯Python和/或字节码。
如果你认为“但这听起来更像宏而不是预处理程序的黑客,如果只有Python有宏”......那么你可能更喜欢使用一个给Python提供宏的预处理程序黑客,比如MacroPy,然后将你的扩展编写为宏。
selfcaller
适用于实际的fib
函数,但 Y 组合子适用于返回fib
函数的函数。绑定发生在创建函数的函数的形式参数中。这只是一个本地变量,没有理由将其作为参数。此外,内部函数没有必要是lambda
而不是def
- 在这种情况下,您已经拥有了本地名称。这意味着 Y 组合子没有添加任何内容;你所需要的装饰器只是调用创建函数的函数。这就是... 我的答案。 - abarnert(x,y) -> z
等价于高阶函数x -> (y -> z)
,因此selfcaller
等同于Y组合子。 - Abe Karplusfib
一样。 - abarnert