namedtuple

collections.namedtuple(typename, filed_name, *, rename=False, module=None)

创建一个以 typename 命名的 tuple 子类,这个子类用于创建类元组对象,这些对象可以像元组一样被索引和迭代。

field_name: 指定 namedtuple 的字段名,可以是列表,可以是用空格或逗号隔开的字符串。

rename: field_name 中无效的标识符(字符,数字,下划线,并且不以下划线或数字开头,不与内置变量名冲突)将被自动替换为下划线加索引(例:_1),

module: 设置__module__的值,默认为 '__main__'

from collections import namedtuple

Point = namedtuple('Point', ['x', 'y'])
a = Point(1, 2)

输出:

>>> a[0]
1
>>> a[1]
2
>>> a.x
1
>>> a.y
2
>>> c,d = a
>>> c, d
(1, 2)

namedtuple 除了继承 tuples 的方法外,还有3个额外的方法和2个额外的属性:

classmethod  somenamedtuple._make(iterable)

用已存在的列表或可迭代对象创建 namedtuple 实例:

>>> t = [1, 2]
>>> Point._make(t)
Point(x=1, y=2)

somenamedtuple._asdict()

将 namedtuple 转变为 OrderDict

>>> p = Point(x=11, y=22)
>>> p._asdict()
OrderedDict([('x', 11), ('y', 22)])

somenamedtuple._replace()

替换属性值,返还一个新的对象

>>> p = Point(x=11, y=22)
>>> p._replace(x=33)
Point(x=33, y=22)

_replace() 还有一个很有用的特性,就是当你的命名元组拥有可选字段或者缺失字段的时候,它能够方便的添加数据。

from collections import namedtuple
Stock = namedtuple('Stock', ['name', 'shares', 'price', 'date', 'time'])
# Create a prototype instance
stock_prototype = Stock('', 0, 0.0, None, None)
# Function to convert a dictionary to a Stock
def dict_to_stock(s):
return stock_prototype._replace(**s)
>>> a = {'name': 'ACME', 'shares': 100, 'price': 123.45}
>>> dict_to_stock(a)
Stock(name='ACME', shares=100, price=123.45, date=None, time=None)
>>> b = {'name': 'ACME', 'shares': 100, 'price': 123.45, 'date': '12/17/2012'}
>>> dict_to_stock(b)
Stock(name='ACME', shares=100, price=123.45, date='12/17/2012', time=None)
>>>

somenamedtuple._source()

somenamedtuple._fields

返回一个元祖,包含所有的属性

>>> p._fields            # view the field names
('x', 'y')
>>> Color = namedtuple('Color', 'red green blue')
>>> Pixel = namedtuple('Pixel', Point._fields + Color._fields)
>>> Pixel(11, 22, 128, 255, 0)
Pixel(x=11, y=22, red=128, green=255, blue=0)

将字典转变为 namedtuple:

>>> d = {'x': 11, 'y': 22}
>>> Point(**d)
Point(x=11, y=22)

默认值可以通过 _replace() 来重新定义,例:

>>> Account = namedtuple('Account', 'owner balance transaction_count')
>>> default_account = Account('<owner name>', 0.0, 0)
>>> johns_account = default_account._replace(owner='John')
>>> janes_account = default_account._replace(owner='Jane')
# _replace()返回一个新的实例

 源码分析

def namedtuple(typename, field_names, *, rename=False, defaults=None, module=None):
    """Returns a new subclass of tuple with named fields.

    >>> Point = namedtuple('Point', ['x', 'y'])
    >>> Point.__doc__                   # docstring for the new class
    'Point(x, y)'
    >>> p = Point(11, y=22)             # instantiate with positional args or keywords
    >>> p[0] + p[1]                     # indexable like a plain tuple
    33
    >>> x, y = p                        # unpack like a regular tuple
    >>> x, y
    (11, 22)
    >>> p.x + p.y                       # fields also accessible by name
    33
    >>> d = p._asdict()                 # convert to a dictionary
    >>> d['x']
    11
    >>> Point(**d)                      # convert from a dictionary
    Point(x=11, y=22)
    >>> p._replace(x=100)               # _replace() is like str.replace() but targets named fields
    Point(x=100, y=22)

    """

    # Validate the field names.  At the user's option, either generate an error
    # message or automatically replace the field name with a valid name.
    if isinstance(field_names, str):
        field_names = field_names.replace(',', ' ').split()
    field_names = list(map(str, field_names))
    typename = _sys.intern(str(typename)) # 使用字符串驻留机制
    if rename:
        seen = set()
        for index, name in enumerate(field_names):
       # enumerate 将一个可遍历的数据对象组合成一个索引序列, 返回一个enumerate对象,可以用for遍历,返回索引和值
       # isidentifier 判断变量名知否合法, _iskeyword() 判断是否为内置关键字
if (not name.isidentifier() or _iskeyword(name) or name.startswith('_') or name in seen): field_names[index] = f'_{index}' # f'' ==> 字符串格式化 seen.add(name) for name in [typename] + field_names: if type(name) is not str: raise TypeError('Type names and field names must be strings') if not name.isidentifier(): raise ValueError('Type names and field names must be valid ' f'identifiers: {name!r}') # !r 反映对象本体 if _iskeyword(name): raise ValueError('Type names and field names cannot be a ' f'keyword: {name!r}') seen = set() for name in field_names: if name.startswith('_') and not rename: raise ValueError('Field names cannot start with an underscore: ' f'{name!r}') if name in seen: raise ValueError(f'Encountered duplicate field name: {name!r}') seen.add(name)
# 为字段设置默认值,靠右 field_defaults
= {} if defaults is not None: defaults = tuple(defaults) if len(defaults) > len(field_names): raise TypeError('Got more default values than field names') field_defaults = dict(reversed(list(zip(reversed(field_names), reversed(defaults))))) # Variables used in the methods and docstrings field_names = tuple(map(_sys.intern, field_names)) num_fields = len(field_names) arg_list = repr(field_names).replace("'", "")[1:-1] repr_fmt = '(' + ', '.join(f'{name}=%r' for name in field_names) + ')' tuple_new = tuple.__new__ _len = len # Create all the named tuple methods to be added to the class namespace s = f'def __new__(_cls, {arg_list}): return _tuple_new(_cls, ({arg_list}))' namespace = {'_tuple_new': tuple_new, '__name__': f'namedtuple_{typename}'} # Note: exec() has the side-effect of interning the field names exec(s, namespace) # 执行 s 中的python 语句,namespace为参数,将__new__添加至 namespace __new__ = namespace['__new__'] __new__.__doc__ = f'Create new instance of {typename}({arg_list})' if defaults is not None: __new__.__defaults__ = defaults @classmethod def _make(cls, iterable): result = tuple_new(cls, iterable) # 实例化 if _len(result) != num_fields: raise TypeError(f'Expected {num_fields} arguments, got {len(result)}') return result _make.__func__.__doc__ = (f'Make a new {typename} object from a sequence ' 'or iterable') def _replace(_self, **kwds): result = _self._make(map(kwds.pop, field_names, _self)) if kwds: raise ValueError(f'Got unexpected field names: {list(kwds)!r}') return result _replace.__doc__ = (f'Return a new {typename} object replacing specified ' 'fields with new values') def __repr__(self): 'Return a nicely formatted representation string' return self.__class__.__name__ + repr_fmt % self def _asdict(self): 'Return a new OrderedDict which maps field names to their values.' return OrderedDict(zip(self._fields, self)) def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return tuple(self) # Modify function metadata to help with introspection and debugging for method in (__new__, _make.__func__, _replace, __repr__, _asdict, __getnewargs__): method.__qualname__ = f'{typename}.{metho  .__name__}' # Build-up the class namespace dictionary # and use type() to build the result class
# 类的各个属性和方法
class_namespace = { '__doc__': f'{typename}({arg_list})', '__slots__': (), '_fields': field_names, '_fields_defaults': field_defaults, '__new__': __new__, '_make': _make, '_replace': _replace, '__repr__': __repr__, '_asdict': _asdict, '__getnewargs__': __getnewargs__, } cache = _nt_itemgetters
# 将属性与属性值对应起来
for index, name in enumerate(field_names): try: itemgetter_object, doc = cache[index] except KeyError: itemgetter_object = _itemgetter(index) doc = f'Alias for field number {index}' cache[index] = itemgetter_object, doc class_namespace[name] = property(itemgetter_object, doc=doc) result = type(typename, (tuple,), class_namespace) # 动态创建类 # For pickling to work, the __module__ variable needs to be set to the frame # where the named tuple is created. Bypass this step in environments where # sys._getframe is not defined (Jython for example) or sys._getframe is not # defined for arguments greater than 0 (IronPython), or where the user has # specified a particular module. if module is None: try: module = _sys._getframe(1).f_globals.get('__name__', '__main__') except (AttributeError, ValueError): pass if module is not None: result.__module__ = module return result