实现python namedtuple元类编程
作者:weapon
起步
namedtuple
是 collections
模块下的一个功能,它是类工厂函数,能返回 tuple
子类,允许通过字段名向元组中取值,性能上接近于元组。
Person = namedtuple('Person', 'name age') p = Person(name='Tony', age=18) print(p.name) # 'Tony' print(p.age) # 18
我们来试着自己动手来实现这个 namedtuple
功能。因为这个功能需求明确,没什么模块依赖,通过自己的实现后再去看看真正在 cpython
里的源码就会比较清晰。
需求分析
对于代码 Person = namedtuple('Person', 'name age')
让它等价于:
class Person(tuple): def __new__(cls, name, age): return tuple.__new__(cls, (name, age)) @property def name(self): return self[0] @property def age(self): return self[1]
那么有一种实现就是,拼凑成这样的代码块字符串,再通过 exec(code)
来创建类,旧版本(小于 3.7
)的 namedtuple
在 cpython 中还真就是这么实现的。
PS: 重写了 __new__
而不是 __init__
,有一个原因是因为元组一旦创建就不可变。为了能够通过字段名取值,这里引入了 property
修饰符。
构造类的字符串模板创建类
基于这个思路一个简陋的就能写了出来:
# 类名称模板 _class_template = ''' class {typename}(tuple): def __new__(_cls, {arg_list}): return tuple.__new__(_cls, ({arg_list})) {field_defs} ''' # 属性模板 _field_template = ''' @property def {name}(self): return self[{index}] ''' def namedtuple(typename, field_names): field_names = field_names.split() class_definition = _class_template.format( typename=typename, arg_list=arg_list = repr(field_names).replace("'", "")[1:-1], field_defs=''.join(_field_template.format(index=index, name=name) for index, name in enumerate(field_names)) ) namespace = {} exec(class_definition, namespace) return namespace[typename] # use demo Person = namedtuple('Person', 'name age') p = Person(name='Tony', age=18) print(isinstance(p, tuple)) # True print(p.name) # Tony print(p.age) # 18
cpython
中的 namedtuple
便是基于这个思路实现的
源码:https://github.com/python/cpy...,这个实现方式一直沿用到了 3.6.x 。
直到因为性能原因而进行了改版,PR见:https://github.com/python/cpy...
基于元类编程
改进后的 namedtuple
更能体现元类编程的思想,对性能也有明显的改善。我用简化的代码来展示改版后的 namedtuple
的工作内容:
def _tuplegetter(index): @property def _getter(self): return self[index] return _getter def namedtuple(typename, field_names): field_names = field_names.split() arg_list = repr(field_names).replace("'", "")[1:-1] s = f'def __new__(_cls, {arg_list}): return tuple.__new__(_cls, ({arg_list}))' namespace = {} exec(s, namespace) __new__ = namespace['__new__'] class_namespace = {'__new__': __new__} for index, name in enumerate(field_names): class_namespace[name] = _tuplegetter(index) result = type(typename, (tuple,), class_namespace) return result
到此,一个简易版的 namedtuple
就结构就完成了,改进后的 exec
调用中只有一行代码,性能会更好,旧版本的还会额外 import 其他依赖。然后就是构造元类编程中类属性和方法了。属性的获取是通过写的 _getter
来完成,而实际上源码上会委托给 operator.itemgetter
函数。
总结
相信从本文中理解了 namedtuple
的设计和实现原理,再去阅读源代码,能更快的理解源码,起到事半功倍的效果。
以上就是实现python namedtuple元类编程的详细内容,更多关于python namedtuple元类的资料请关注脚本之家其它相关文章!