【问题标题】:How to modify the behavior of def in Python?如何在 Python 中修改 def 的行为?
【发布时间】:2018-08-25 06:29:30
【问题描述】:

Python 中的大多数东西都可以轻松修改,包括能够使用您最喜欢的代码对象直接使用 Types.FunctionType 实例化函数。并不是说必须这样做,但作为一种学习经验,我试图弄清楚如何从 Python 本身 修改 def 的行为(修改语言定义感觉像是在欺骗这个)。

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

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

【问题讨论】:

  • 你在想什么样的修改?
  • @KlausD。在我看来,修改看起来像是能够指定用于实例化函数的类,而不是使用 Types.FunctionType 或任何默认值,我想我很好奇是否有任何选项。
  • Python 中有不同类型的“可调用对象”、函数、方法来命名最常见的。还可以选择在类中实现__call__(),这将使类实例的行为类似于自定义函数。
  • @KlausD。同意,并且使用__build_class__ 钩子几乎可以得到我正在寻找的效果。不过,定义函数的语法比我为类和其他可调用对象想出的任何语法都要简洁。此外,除了好奇之外,我还没有真正的用例,我想知道它是否可以用函数本身来完成。

标签: python metaprogramming


【解决方案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 的副本,并且在一个模块中搞砸它会影响所有模块。为了减轻损失,我们将把所有的 voodoo 移到它自己的模块中,为了简单起见,我称之为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 元类是创建 vanilla 类的标准方法,因此我们制作了一个。为了真正调用我们刚刚创建的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 的内部有一些好的文档,我将非常感激。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多