如何修改 Python 中 def 的行为?

How to modify the behavior of def in Python?

Python 中的大部分内容都可以轻松修改,包括能够使用您喜欢的代码对象直接通过 Types.FunctionType 实例化函数。不一定有人应该这样做,但作为一种学习经验,我试图弄清楚如何在 Python 本身 中从 修改 def 的行为(修改语言定义对这个感觉就是作弊)

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

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

这里有龙。继续后果自负。

从压倒性的缺乏回应和我仍然无法找到我想要的功能的文档来判断,我要冒昧地说它不存在。也就是说,您可以 修补 class 定义,使其表现得像函数定义 。很有趣,对吧?

在 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

现在检查代码的输出:

>>> fib(n=3)
3

>>> fib(n=4)
5

>>> fib()
8

>>> fib(n=10)
89

我们可以像调用具有默认参数的函数一样调用此 class 并获得正确的值。请注意完全没有 __init__()__new__() 或任何其他 dunder 方法。

警告:您可能并不感到惊讶,但以下内容绝对不是生产就绪的(抱歉,我还在学习中)。

我们选择的武器是超越 builtins.__build_class__ 以获取我们自己的利益。请注意,builtins 在一个 python 应用程序中只有一个副本,在一个模块中将其弄乱会影响所有模块。为了减轻损害,我们将把所有的巫毒都移到它自己的模块中,为了简单起见,我只是称之为 base

我选择覆盖的方式是允许每个模块向 base 注册自己,以及他们想应用于 class 函数的装饰器(为什么去如果你不对所有的人都做点什么,还需要修改每个 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__]

这看起来有很多代码,但它所做的只是创建一个字典 _overrides 并允许来自任何模块的代码在该字典中注册自己。如果他们想使用外部函数但仍然有奇怪的 class 行为只适用于他们自己,我们允许模块明确地将自己传递给 register() 函数。

在我们开始摆弄任何东西之前,我们需要存储旧的 __build_class__() 函数,以便我们可以在任何未注册的模块中使用它。

_obc = builtins.__build_class__

新的 __build_class__() 函数然后只检查模块是否已注册。如果是这样,它会发挥一些作用,否则它会调用原始的内置函数。

def _bc(f, name, *a, mc=None, **k):
    mc = type if mc is None else mc
    try:
        w = _overrides[f.__module__]
    except KeyError:
        return _obc(f, name, *a, metaclass=mc, **k)
    return _cbc(f, name, w, *a, **k)

请注意,class 的默认类型是 type。此外,我们显式地将包装器 w_overrides 传递到我们的自定义方法 _cbc() 中,因为我们无法控制 revoke()。如果我们只是检查一个模块是否已注册,那么用户很可能会在我们查询 _overrides 包装器之前取消注册它。

就将 class 视为代码的魔力而言,它是 __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

通过这个过程,函数 _cbc() 获取函数对象 f return 由 Python 解释器读取我们的 class 定义和将它的代码直接传递给 exec()。如果您碰巧将任何关键字参数传递给函数 g(),它也很乐意将它们扔进 exec()。当一切都说完了,你的 class-函数应该给 res 赋值,所以我们 return 那。

不过,我们仍然需要实际创建一个 class。 type metaclass 是创建原版 class 的标准方法,因此我们制作了一个。要实际调用我们刚刚创建的 g() 东西,我们将它分配给新的 class 上的 __new__(),这样当有人试图实例化我们的 class 时,所有东西都会传递给 __new__()(以及我们不关心的额外 self 参数)。

最后,我们用自定义方法覆盖内置函数。

builtins.__build_class__ = _bc

要使用新玩具,我们需要进口它们。我给我的图书馆打电话 base,但你几乎可以使用任何东西。

import base

然后 base.register() 是开始改变 class 定义工作方式的钩子。我们的惰性实现需要传入一个函数,所以我们可以直接使用标识。

base.register(lambda f: f)

此时,开头的斐波那契代码将完全按照宣传的方式工作。如果你想要一个正常的class,只需调用base.revoke(lambda:1)来暂时排除当前模块的异常行为。

为了让事情变得有趣,我们可以应用影响以这种方式定义的每个 class 函数的包装器。您可以将其用于某种日志记录或用户验证。

import datetime

def verbose(f):
    def _f(*a,**k):
        print (f'Running at {datetime.datetime.now()}')
        return f(*a,**k)
    return _f

base.register(verbose)

>>> fib(n=10)
    Running at 2018-08-25 06:07:56.258317
89

最后一次,这还没有准备好生产。在 class 函数中嵌套函数定义工作正常,但其他类型的嵌套和递归有点混乱。我处理闭包的天真方式非常脆弱。如果有人对 Python 的内部结构有一些好的文档,我将不胜感激。