没有办法做你想做的事。你是对的,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。但在这种情况下,“它”是你可能不想做的事情,所以这并不重要。
self
这是您可以为使用而不是自己的名称的函数编写通用的纯 python 装饰器的一种方法,而无需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,您可以创建一个导入钩子,将您的代码从自定义扩展的 Python 预处理或编译defrec
成纯 Python 和/或字节码。
如果你在想“但是如果只有 Python 有宏,这听起来作为一个宏比作为一个预处理器黑客要好得多”......那么你可能更喜欢使用提供 Python 宏的预处理器黑客,比如MacroPy,然后将扩展编写为宏。