如何修改 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 的内部结构有一些好的文档,我将不胜感激。
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 的内部结构有一些好的文档,我将不胜感激。