[英]How to modify the behavior of def in Python?
Python中的大多数内容都可以轻松修改,包括能够使用您喜欢的代码对象直接使用Types.FunctionType实例化函数。 不是一个人应该这样做,但作为一种学习经验,我正试图弄清楚如何在Python本身内修改def
的行为(修改语言定义感觉就像为这个作弊)。
对于类,使用__build_class__
钩子可以在3.1+中相当容易地完成。 是否有用于挂钩功能建设的类似机器?
到目前为止,我已经尝试修改compile()
, eval()
, exec()
, type()
, Types.FunctionType
,以及在我能找到的任何地方似乎相关的任何其他内容。 据我所知,当def
创建一个函数对象并将其加载到globals()
时, def
不执行任何这些操作。 在尽可能详细的细节中,当我们定义一个函数时到底发生了什么? 整个过程是在底层C代码的幕后完成的吗?
这里有龙。 继续自担风险。
从压倒性的缺乏回应和我仍然无法找到我想要的功能的文档来判断,我将走出困境并说它不存在。 也就是说,您可以修补类定义,使其行为类似于函数定义 。 有趣,对吧?
在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
元类是创建vanilla类的标准方法,因此我们创建一个类。 为了实际调用我们刚刚创建的g()
事物,我们将它分配给新类的__new__()
,这样当有人试图实例化我们的类时,所有东西都被传递到__new__()
(以及我们不需要的额外self
论证)不关心)。
最后,我们使用自定义方法覆盖内置函数。
builtins.__build_class__ = _bc
要使用新玩具我们需要导入它们。 我打电话给我的图书馆base
,但你几乎可以使用任何东西。
import base
然后base.register()
是开始改变类定义工作方式的钩子。 我们的延迟实现需要传入一个函数,所以我们可以使用该标识。
base.register(lambda f: f)
此时,Fibonacci代码从一开始就完全像宣传的那样工作。 如果你想要一个普通的类,只需调用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内部的好文档,我将非常感激。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.