CPython中函数引用它自身

CPython中函数引用它自身

⚠️ 请确保CPython的版本不低于3.8,因语法涉及2个PEPPEP570PEP572

问题

启发自这篇问答:Is there a generic way for a function to reference itself?
曾有一个提议PEP 3130 – Access to Current Module/Class/Function,但是因为方案不完善而被拒绝了

就像__class__在函数体内指向于调用这个函数的类,是否有一个指针或者说魔法关键词,例如__func__,在函数体内使用时,能指向被调用的函数本身?因此,我可以写出像下面这样的代码:

1
2
3
4
5
def foo():
print(__func__.__name__)
print(__func__.__hash__)
print(__func__.__code__)
... # other simliars

解决方案

暂时还没有这样的一个指针和魔法关键词,起码就python 3.8来看。因此暂时只能用一些侵入式的方案,来模拟实现这一需求。

通过调用栈信息来获取函数引用

我可以在一个函数的函数体内访问这个函数本身,通过以下代码:

1
2
3
4
5
def foo():
print(foo.__name__)
print(foo.__hash__)
print(foo.__code__)
... # other simliars

因为以上方式需要显式地指定函数名,当函数名发生改变时,函数体内相应的名字也要进行改变。为了避免这一点,需要函数有充足的信息以定位自身。例如,当可以通过函数在定义时的名字在相应的全局命名空间中查找引用时

这个名字存储于function.__code__.co_name:如果用def func_name或者async def func_name创建函数,func_name就是名字;如果用lambda创建函数,名字就是<lambda>,我特别不建议如此。
⚠️ 这个属性co_name是只读的且不能被改变。但你可以自己创建一个新的code对象,复制相同的功能,选用不同的名字。
⚠️ 这里并没有用到function.__name__,这个属性是可变的,而且更加灵活和合适,但是相对来说获取困难,所以不采用。特别是,function.__qualname__也同样难以获得,不然可以统一地解决很多问题了。
Note Name of a Python function

,就可以使用以下帮助函数来获取这个函数的引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import sys

def getfuncref():
'''
Get a reference to the `function` which is called recently
in this thread,as long as the key where in `globals` that
point to the function is equal to `function.__code__.co_name`.
'''
# REF: https://www.oreilly.com/library/view/python-cookbook/0596001673/ch14s08.html
# This introspective task is easily performed with sys._getframe.
# This function returns a frame object whose attribute f_code is
# a code object and the co_name attribute of that object is the
# function name.
# By calling sys._getframe(1), you can get this information
# for the caller of the current function. So you can package
# this functionality into your own handy functions.
# This calls sys._getframe with argument 1, because the call
# to `getfuncref` is now frame 0.
f = sys._getframe(1)
return f.f_globals[f.f_code.co_name]

See Also

测试结果如下:

1
2
3
>>> def foo(): return getfuncref()
>>> foo is foo()
True

Note 截至python 3.8,调用栈的帧frameframe-objects)上并没有对函数对象的引用,只是引用了函数相应的code对象,以及相关的执行环境信息。因此,通过审查调用栈来获取函数function调用时对应的帧frame的信息,以获取function的引用,需要根据一些信息以间接方式在相关的命名空间上搜索。而且由于code对象上没有存储函数的描述信息(尽管这种策略有利于code对象的复用),如果函数定义在类中,那么定位这个函数的策略将会比较复杂,因为有一些不同的情况需要分别对待,而且可能还需要一些另外的信息。
🔔 我觉得你可以利用inspect模块来辅助收集信息。

通过把函数的引用绑定到参数上

可以通过在调用函数时传入这个函数的引用,在函数体内使用:

1
2
3
4
5
def foo(__func__, /):
print(__func__.__name__)
print(__func__.__hash__)
print(__func__.__code__)
... # other simliars

为此,我设计了3个装饰器来实现一种绑定效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from functools import partial, update_wrapper

############### plan 1 ###############
def partial_bind_func(f, /):
'Decorate function `f` to pass a reference to the function as the first argument'
return update_wrapper(partial(f, f), f)

############### plan 2 ###############
def method_bind_func(f, /):
'Decorate function `f` to pass a reference to the function as the first argument'
return f.__get__(f, type(f))

############### plan 3 ###############
import inspect

def assign_bind_func(f, /):
'Decorate function `f` to pass a reference to the function as the first argument'
wrapper = update_wrapper(lambda *a, **k: f(f, *a, **k), f)
params = tuple(inspect.signature(f).parameters.values())
wrapper.__signature__ = inspect.Signature(params[1:])
return wrapper

测试结果如下:

1
2
3
4
5
6
7
8
9
>>> def foo(__func__, /, a, *b, c, **d): pass
>>> inspect.signature(foo)
<Signature (__func__, /, a, *b, c, **d)>
>>> inspect.signature(partial_bind_func(foo), follow_wrapped=False)
<Signature (a, *b, c, **d)>
>>> inspect.signature(method_bind_func(foo))
<Signature (a, *b, c, **d)>
>>> inspect.signature(assign_bind_func(foo))
<Signature (a, *b, c, **d)>
# Python

评论

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×