exec函数_Python骚操作:动态定义函数

43164bf4c852984bca745b6aac49ecf8.png

标题:Python Tips: Dynamic function definition

作者:Philip Trauner

译者:豌豆花下猫

链接:

https://philip-trauner.me/blog/post/python-tips-dynamic-function-definition


在 Python 中,没有可以在运行时简化函数定义的语法糖。然而,这并不意味着它就不可能,或者是难以实现。

from types import FunctionType

foo_code = compile('def foo(): return "bar"', "", "exec")
foo_func = FunctionType(foo_code.co_consts[0], globals(), "foo")

print(foo_func())

输出:

bar

剖析

逐行检视代码,你会发现语言/解释器的屏障是多么脆弱。

>>> from types import FunctionType

Python 文档通常不会列出那些非用于手动创建的类的特征(这是完全合理的)。有三种方法可以解决这个问题:help()、inspect(无法查看内置方法)、以及最后的解决方案,即查看 CPython 源代码。

在本例中,help() 与 inspect 都可以完成工作,但是查看实际的源代码,则会揭示出关于数据类型的更多细节。

>>> from inspect import signature
>>> signature(FunctionType)
None, argdefs=None, closure=None)>

1. code

内部是一个PyCodeobject,作为types.CodeType对外开放。非内置方法拥有一个__code__属性,该属性保存了相应的代码对象。利用内置 compile() 方法,可以在运行期创建types.CodeType对象。

2. globals

如果一个函数引用的变量不是在局部定义的,而是作为参数转入、由默认参数值提供、或者通过闭包上下文提供,则它会在 globals 字典中查找。

内置的 globals() 方法会返回一个对当前模块的全局符号表(global symbol table)的引用 ,因此能被用来提供一个总是与当前表的状态相一致的字典。传入任意其它的字典也是可以的(FunctionType((lambda: bar).__code__, {"bar" : "baz"}, "foo")() == "baz")。

3. name(可选)

控制所返回的函数的__name__ 属性。只真正对 lambdas 有用(由于匿名性,它们通常没有名称),并且重命名函数。

4. argdefs(可选)

通过传入一个包含任意类型的对象的元组,提供一个方式来供应默认参数值(def foo(bar="baz"))。(FunctionType((lambda bar: bar).__code__, {}, "foo", (10,))() == 10)。

5. closure(可选)

(如果需要在 CPython(PyPy,Jython,…)以外的其它 Python VM 中执行,可能不应该触及,因为它严重地依赖于实现细节)。

一个cell 对象的元组。创建 cell 对象并非完全是直截了当的,因为需要调用 CPython 的内部组件,但有一个库可以令它更加方便:exalt(无耻的广告)。(译注:这个库是作者开发的。)

>>> foo_code = compile('def foo(): return "bar"', "", "exec")

compile() 是一个内置方法,因此同时也是文档丰富的。

exec 模式被用到,因为定义函数需用多个语句。

>>> foo_func = FunctionType(foo_code.co_consts[0], globals(), "foo")

聚合全部内容,并将动态创建的函数指定给一个变量。

那个被前一句代码编译成的函数,成为了生成的代码对象的第一个常量,因此仅仅指向 foo_code 是不充分的。这是 exec 模式的直接后果,因为生成的代码对象可以包含多个常量。

>>> print(foo_func())

动态生成的函数可以像其它函数一样被调用。

结尾

  • 除了做实验,需要用到动态创建函数的场景很少。

  • 玩耍(Toying around) Python 的内部构件是一种深入学习这门语言的好方法。

  • 如果需要,可以毫不费力地越过解释器/语言的界线。

还是一如既往地:不要滥用语言 (好吧,一点点也无妨,对吧?)

--------(译文完)--------

花下猫语: 

在上一篇《Python进阶:如何将字符串常量转为变量?》中,我介绍了两种动态修改变量的方法(globals() 与 exec())。写完之后,我偶然发现,在自己列的“计划转载清单”中,有这一篇相关的文章,它介绍了动态定义函数的方法。因为它的相关度太大,而篇幅又是那么小(核心代码只有三行,文中其它内容都是在解释其背后的原理),我觉得如果翻译出来的话,效果会更好,所以就抓紧时间翻译出来了。建议与前一篇文章配合阅读。

af16ebd0c98d955b92c7e525d416194f.png

图片来源:pexels

随机推荐,偶遇精彩

1

Python 动态赋值的陷阱

2

详解Python拼接字符串的七种方式

3

为什么range不是迭代器?range到底是什么类型?

4

Python 之父退位后,最高决策权花落谁家?

dc57611bde7a3ed9a200b54714347757.png

一只伪喵星来客

一个有趣又有用的学习分享平台

专注Python技术、数据科学和深度学习

兼具极客思维与人文情怀

欢迎你关注

微信号:python_cat

13b5d6d854af81f28b0f013155b97820.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值