如何修改Python中def的行为?

Han*_*ave 4 python metaprogramming

Python 中的大多数内容都很容易修改,包括能够使用您最喜欢的代码对象直接通过 Types.FunctionType 实例化函数。并不是说人们必须这样做,但作为一种学习经验,我试图弄清楚如何defPython本身内部修改行为(修改语言定义感觉像是在作弊)。

对于类,这可以在 3.1+ 中使用钩子相当容易地完成__build_class__是否有类似的机制用于挂钩函数构建?

到目前为止,我已经尝试修改、、、、、compile()以及在我能找到eval()的任何exec()地方看起来相关的任何其他内容。据我所知,当它创建函数对象并将其加载到. 尽可能详细地说明,当我们定义函数时到底会发生什么?整个过程是在底层C代码中在幕后完成的吗?type()Types.FunctionTypedefglobals()

Han*_*ave 6

这里有龙。继续下去,后果自负。

鉴于压倒性的缺乏回应以及我仍然无法找到我想要的功能的文档,我将冒险说它不存在。也就是说,您可以修补类定义,使其行为类似于函数定义。很有趣,对吧?

在 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 内部结构的一些好的文档,我将非常感激。