動手來本身實現一下 namedtuple

起步

namedtuplecollections 模塊下的一個功能,它是類工廠函數,能返回 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

相關文章
相關標籤/搜索