当前位置: 首页 > 后端技术 > Python

让我们自己实现namedtuple

时间:2023-03-26 17:42:02 Python

namedtuple是collections模块下的一个函数。它是一个可以返回元组子类的类工厂函数。它允许通过字段名从元组中获取值,其性能接近于元组。Person=namedtuple('Person','nameage')p=Person(name='Tony',age=18)print(p.name)#'Tony'print(p.age)#18让我们自己试试吧使用此namedtuple功能会很麻烦。因为这个功能需求很明确,没有模块依赖,所以自己实现之后再看cpython中真正的源码会更清楚。需求分析对于代码Person=namedtuple('Person','nameage')等价于:classPerson(tuple):def__new__(cls,name,age):returntuple.__new__(cls,(name,age))@propertydefname(self):returnself[0]@propertydefage(self):returnself[1]那么有一个实现就是把这样一个代码块字符串拼凑起来,然后用exec(code)来创建一个类,老版本(3.7以下)的namedtuple在cpython中确实是这样实现的。PS:重写__new__而不是__init__的原因之一是元组一旦创建就不可变。为了能够通过字段名获取值,这里引入属性修饰符。构造类的字符串模板创建类可以基于这样的思路来写:#类名模板_class_template='''class{typename}(tuple):def__new__(_cls,{arg_list}):returntuple.__new__(_cls,({arg_list})){field_defs}'''#属性模板_field_template='''@propertydef{name}(self):returnself[{index}]'''defnamedtuple(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)forindex,nameinenumerate(field_names)))namespace={}exec(class_definition,namespace)returnnamespace[typename]#usedemoPerson=namedtuple('Person','nameage'复制代码)p=Person(name='Tony',age=18)print(isinstance(p,tuple))#Trueprint(p.name)#Tonyprint(p.age)#18cpython中的namedtuple就是基于这个思路,见源代码:https://github.com/python/cpy...,这个实现方式一直沿用到3.6.x,直到因为性能原因修改,PR见:https://github.com/python/cpy...基于元类编程改进后namedtuple更能体现元类编程的思想,在性能上有明显的提升。我使用简化的代码来展示修改后的namedtuple是如何工作的:def_tuplegetter(index):@propertydef_getter(self):returnself[index]return_getterdefnamedtuple(typename,field_names):field_names=field_names.split()arg_list=repr(field_names).replace("'","")[1:-1]s=f'def__new__(_cls,{arg_list}):returntuple.__new__(_cls,({arg_list}))'namespace={}exec(s,namespace)__new__=namespace['__new__']class_namespace={'__new__':__new__}forindex,nameinenumerate(field_names):class_namespace[name]=_tuplegetter(index)result=type(typename,(tuple,),class_namespace)returnresult至此,一个简单版的namedtuple就完成了。改进后的exec调用只有一行代码,性能会更好。旧版本会另外引入其他依赖。然后就是元类编程中类属性和方法的构造。属性的获取是通过写好的_getter来完成的,但实际上源码中会委托给operator.itemgetter函数。综上所述,相信通过本文了解namedtuple的设计和实现原理后,再去阅读源码,可以更快的理解源码,达到事半功倍的效果。