Han*_*ave 4 python metaprogramming
Python 中的大多数内容都很容易修改,包括能够使用您最喜欢的代码对象直接通过 Types.FunctionType 实例化函数。并不是说人们必须这样做,但作为一种学习经验,我试图弄清楚如何def从Python本身内部修改行为(修改语言定义感觉像是在作弊)。
对于类,这可以在 3.1+ 中使用钩子相当容易地完成__build_class__。是否有类似的机制用于挂钩函数构建?
到目前为止,我已经尝试修改、、、、、compile()以及在我能找到eval()的任何exec()地方看起来相关的任何其他内容。据我所知,当它创建函数对象并将其加载到. 尽可能详细地说明,当我们定义函数时到底会发生什么?整个过程是在底层C代码中在幕后完成的吗?type()Types.FunctionTypedefglobals()
这里有龙。继续下去,后果自负。
鉴于压倒性的缺乏回应以及我仍然无法找到我想要的功能的文档,我将冒险说它不存在。也就是说,您可以修补类定义,使其行为类似于函数定义。很有趣,对吧?
在 Python 3.1+ 中,以下内容(以及我稍后将介绍的帮助程序文件)是合法代码,其行为大致如您所期望的那样。
class fib(n=5):
if n < 2:
res = 1
a = b = 1
for i in range(2, n+1):
a, b = b, a+b
res = b
Run Code Online (Sandbox Code Playgroud)
现在检查代码的输出:
>>> fib(n=3)
3
>>> fib(n=4)
5
>>> fib()
8
>>> fib(n=10)
89
Run Code Online (Sandbox Code Playgroud)
我们能够像使用默认参数的函数一样调用此类并获取正确的值。请注意,完全缺少__init__()、__new__()或任何其他 dunder 方法。
警告:这可能对你来说并不奇怪,但以下内容绝对没有准备好生产(我仍在学习,抱歉)。
我们选择的武器是builtins.__build_class__为了自己的利益而凌驾于一切之上。请注意,Python 应用程序中只有一份 副本builtins,在一个模块中搞乱它会影响所有模块。为了减轻损害,我们将把所有 voodoo 移动到它自己的模块中,我只是base为了简单起见而称之为它。
我选择进行覆盖的方法是允许每个模块向 注册自身base,以及他们想要应用于其类函数的装饰器(如果您不做某事,为什么要麻烦地修改每个类对他们所有人?)
>>> fib(n=3)
3
>>> fib(n=4)
5
>>> fib()
8
>>> fib(n=10)
89
Run Code Online (Sandbox Code Playgroud)
这看起来有很多代码,但它所做的只是创建一个字典_overrides并允许任何模块中的代码将其自身注册到该字典中。如果他们想要使用外部函数,但仍然有只适用于他们自己的奇怪的类行为,我们允许模块显式地将自己传递到函数中register()。
在我们开始摆弄任何东西之前,我们需要存储旧__build_class__()函数,以便我们可以在任何未注册的模块中使用它。
_obc = builtins.__build_class__
Run Code Online (Sandbox Code Playgroud)
然后新__build_class__()函数只检查模块是否已注册。如果是这样,它就会发挥一些作用,否则它会调用原始的内置函数。
import builtins
_overrides = {}
def register(f, module=None):
module = module if module is not None else f.__module__
_overrides[module] = f
def revoke(x):
try:
del _overrides[x]
except KeyError:
del _overrides[x.__module__]
Run Code Online (Sandbox Code Playgroud)
请注意,类的默认类型是type。此外,我们显式地将包装器传递w到_overrides我们的自定义方法中,_cbc()因为我们无法控制revoke(). 如果我们只是检查模块是否已注册,则用户很可能在我们查询_overrides包装器之前取消注册它。
就将类视为代码的魔力而言,它是__build_code__().
def _cbc(f, name, w, **k):
def g(**x):
for key in k:
if key not in x:
x[key] = k[key]
exec(f.__code__, {}, x)
return x['res']
t = type(name, (), {})
t.__new__ = lambda self, **kwargs: w(g)(**kwargs)
return t
Run Code Online (Sandbox Code Playgroud)
通过这个过程,该函数在读取我们的类定义时_cbc()获取 Python 解释器返回的函数对象,并将其代码直接传递到. 如果您碰巧将任何关键字参数传递给函数,它也会很乐意将它们扔进去。当这一切都说完并完成后,您的类函数预计已为 分配了一个值,因此我们返回该值。fexec()g()exec()res
但我们仍然需要实际创建一个类。元type类是创建普通类的标准方法,因此我们制作了一个。为了实际调用g()我们刚刚创建的东西,我们将它分配给__new__()新类,这样当有人尝试实例化我们的类时,所有内容都会被传递进去__new__()(以及self我们不关心的额外参数)。
最后,我们用自定义方法覆盖内置函数。
builtins.__build_class__ = _bc
Run Code Online (Sandbox Code Playgroud)
要使用新玩具,我们需要进口它们。我打电话给我的图书馆base,但你几乎可以使用任何东西。
import base
Run Code Online (Sandbox Code Playgroud)
然后base.register()是开始改变类定义工作方式的钩子。我们的惰性实现需要传入一个函数,因此我们可以只使用标识。
base.register(lambda f: f)
Run Code Online (Sandbox Code Playgroud)
此时,斐波那契代码从一开始就将完全按照宣传的那样工作。如果你想要一个正常的类,只需调用base.revoke(lambda:1)暂时排除当前模块的异常行为即可。
为了让事情变得有趣,我们可以应用影响以这种方式定义的每个类函数的包装器。您可以将其用于某种日志记录或用户验证。
_obc = builtins.__build_class__
Run Code Online (Sandbox Code Playgroud)
最后一次,这还没有准备好投入生产。在类函数内嵌套函数定义工作正常,但其他类型的嵌套和递归有点混乱。我处理闭包的幼稚方式非常脆弱。如果有人有关于 Python 内部结构的一些好的文档,我将非常感激。