如何在Python中修改def的行为?

时间:2018-08-25 06:29:31

标签: python metaprogramming

Python中的大多数内容都可以轻松修改,甚至可以使用您喜欢的代码对象直接使用Types.FunctionType实例化函数。并非一定要这样做,但是作为一种学习经验,我试图找出如何在Python本身之内从修改def的行为(修改语言定义的感觉就像为这个作弊)。

对于类,可以使用__build_class__钩在3.1+中轻松完成。 是否存在用于构建挂钩功能的类似机器?

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

1 个答案:

答案 0 :(得分:1)

这里有龙。继续自己承担风险。

基于对响应的绝对缺乏以及我仍然无法找到我想要的功能的文档来判断,我将不知所措,说它不存在。也就是说,您可以修补类定义,使其行为类似于函数定义。有趣,对吧?

在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

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

警告:这可能不足为奇,但是以下内容显然还没有准备就绪(抱歉,我仍在学习中)。

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

我选择进行覆盖的方式是允许每个模块向base进行注册,以及它们希望将其应用于类函数的装饰器(为什么要麻烦地修改每个模块,上课是否对所有人都没有做?)

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并允许来自任何模块的代码将自身注册到该字典中。如果他们想使用外部函数,但仍然具有怪异的类行为仅适用于自身,我们允许模块将其显式传递给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)

请注意,类的默认类型为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

在此过程中,函数_cbc()接受Python解释器返回的函数对象f,因为它读取我们的类定义并将其代码直接传递到exec()中。如果您碰巧将任何关键字参数传递给函数g(),它也会很高兴地将它们传递到exec()中。说完这些之后,您的类函数应该已经为res分配了一个值,因此我们将其返回。

尽管如此,我们仍然必须实际创建一个类。 type元类是创建普通类的标准方法,因此我们创建了一个。为了实际调用我们刚刚创建的g(),我们将其分配给新类上的__new__(),这样,当有人尝试实例化我们的类时,所有内容都将传递给__new__()(以及一个我们不在乎的额外self参数)。

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

builtins.__build_class__ = _bc

要使用新玩具,我们需要将其导入。我叫我的图书馆base,但您几乎可以使用任何东西。

import base

然后base.register()是开始更改类定义的工作方式的钩子。我们的惰性实现需要传入一个函数,因此我们可以使用身份。

base.register(lambda f: f)

这时,斐波那契代码从一开始就将完全按照广告中的说明工作。如果您要使用普通班级,只需调用base.revoke(lambda:1)即可暂时将当前模块排除在异常行为之外。

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

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

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