namedtuple
是 collections
模塊下的一個功能,它是類工廠函數,能返回 tuple
子類,容許經過字段名向元組中取值,性能上接近於元組。python
Person = namedtuple('Person', 'name age') p = Person(name='Tony', age=18) print(p.name) # 'Tony' print(p.age) # 18
咱們來試着本身動手來實現這個 namedtuple
功能。由於這個功能需求明確,沒什麼模塊依賴,經過本身的實現後再去看看真正在 cpython
裏的源碼就會比較清晰。git
對於代碼 Person = namedtuple('Person', 'name age')
讓它等價於:github
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...spa
改進後的 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
函數。code
相信從本文中理解了 namedtuple
的設計和實現原理,再去閱讀源代碼,能更快的理解源碼,起到事半功倍的效果。orm