Python Tips: Dynamic function definition

In Python, there isn't any syntactic sugar that eases function definition during runtime. However, that doesn't mean that its impossible or even difficult.

from types import FunctionType

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

print(foo_func())

Output

bar

Dissection

Stepping through the code line-by-line reveals how thin the language / interpreter barrier really is.

>>> from types import FunctionType

The Python docs often do not list the signatures of classes that aren't meant to be created manually (which is completely reasonable).
There are three ways to work around this: help()inspect (which can't examine built-in functions), and the fallback solution of looking at the CPython source code.
In this instance both help() and inspect do the job, but looking at the actual source code reveals additional details in regards to data-types.

>>> from inspect import signature
>>> signature(FunctionType)
<Signature (code, globals, name=None, argdefs=None, closure=None)>
  1. code
    Internally a PyCodeObject, which is exposed as a types.CodeType.
    Non-built-in functions have a __code__ attribute which holds their corresponding code object.
    types.CodeType objects can be created at runtime by utilizing the compile() built-in.
  2. globals
    If a variable that is being referenced in a function isn't defined locally, passed in as a parameter, provided by a default argument value, or supplied through a closure context, it is looked up in the globals dictionary.
    The globals() built-in function returns a reference to the global symbol table of the current module, and can therefor be used to supply a dictionary that is always up-to-date with the current state of said table. Passing in any other dictionary works as well (FunctionType((lambda: bar).__code__, {"bar" : "baz"}, "foo")() == "baz").
  3. name (optional)
    Controls the __name__ attribute of the returned function. Only really useful for lambdas (due to their anonymous nature they normally don't have names), and renaming functions.
  4. argdefs (optional)
    Provides a way to supply default argument values (def foo(bar="baz")) by passing in a tuple that contains objects of arbitrary type. (FunctionType((lambda bar: bar).__code__, {}, "foo", (10,))() == 10).
  5. closure (optional)
    (Probably shouldn't be touched if execution in any Python VM other than CPython (PyPy, Jython, ...) is desired, as it heavily relies on implementation details)
    tuple of cell objects. Creating cell objects isn't exactly straight forward, because calling into CPython internals is required, but there is a library that makes this more convenient: exalt (shameless advertisement).
>>> foo_code = compile('def foo(): return "bar"', "<string>", "exec")

compile() is a built-in function, and therefor also well documented.

exec mode is utilized, because multiple statements are necessary to define a function.

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

Bringing it all together and assigning the dynamically created function to a variable.
The function that was compiled in the previous statement becomes the first constant of the generated code object, therefor just referring to foo_code is insufficient. This is a direct consequence of exec mode, because the resulting code object can contain multiple constants.

>>> print(foo_func())

The dynamically created function can be called just like any other function.

Conclusion

  • There are very few use-cases for dynamic function creation outside of experimentation.
  • Toying around with Python internals is a great way to learn more about the language.
  • The interpreter / language boundary can be crossed rather effortlessly if desired.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值