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('', 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