文章的Python版本为3.5
namedtuple使用
这个函数用来创建tuple子类,可以用名称字段。例如下面的代码演示它的特效
- 从
collections
中导入,函数第一个参数为类名,第二个为要定义的名称字段,它们用逗号分割,放入一个字符串中。之后就能通过这个类来创建对象,代码演示中新建的类叫Someone
>>> from collections import namedtuple >>> Person = namedtuple('Person', 'name, age') # 此时Person这个类就有name,age两个字符 >>> Someone = Person('ss', 29) # 新建一个name为ss,Someone >>> Someone = Person(name='ss', age=29) # 参数可以用key-value来表示
__doc__
为新类的描述文档,由namedtuple新建>>> Person.__doc__ Person(name, age)
- 因为是tuple的子类,可以直接通过索引来访问属性,也可以通过字段名称来访问。同时能够像tuple那样解包赋值
>>> Someone[0] # 通过下标访问 ss >>> Someone.name # 通过字段名来访问 ss >>> name, age = Someone ss 29
- 可以转换为
dict
,得到的是OrderedDict
。同样通过一个dict
来构造Someone
这个我们定义的类>>> d = Someone._asdict() >>> d = { 'name': 'ss', 'age': 33 } >>> anothor = Someone(**d) # 将d解包后传入构造函数
- 使用类方法
_make
来生成一个Someone
对象>>> ss = Someone._make(['myname', 28]) >>> ss Person(name='myname', age=28)
- 使用
_replace
来通过替换特定的字段和值,返回一个新的对象>>> older = ss._replace(age=50) # 上一个函数_make创建的ss >>> older Person(name='myname', age=50)
namedtuple实现
namedtuple函数使用的是元编程技术,通过传入namedtuple(cls_name, field_names)
参数来定制类。
-
首先看
namedtuple
中对参数field_names
的处理。如果参数是字符串,则将其分割成数组,比如'name, age'
变为['name', 'age']
。同时参数可以是可迭代对象,也将其转为字符串数组if isinstance(field_names, str): field_names = field_names.replace(',', ' ').split() field_names = list(map(str, field_names)) typename = str(typename)
-
检查传入
field_names
是否符合要求,不能是Python关键词,也不能是运算符。for name in [typename] + field_names: if type(name) != 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 ' 'identifiers: %r' % name) if _iskeyword(name): # 不能是Python关键词 raise ValueError('Type names and field names cannot be a ' 'keyword: %r' % name) seen = set() for name in field_names: if name.startswith('_') and not rename: # 不能以'_'开头 raise ValueError('Field names cannot start with an underscore: ' '%r' % name) if name in seen: # 不能重复定义标识符 raise ValueError('Encountered duplicate field name: %r' % name) seen.add(name)
-
在
namedtuple
定义了一个模板_class_template
,里面包括前面提到的各种方法,下面是模板的代码:from builtins import property as _property, tuple as _tuple from operator import itemgetter as _itemgetter from collections import OrderedDict class {typename}(tuple): '{typename}({arg_list})' __slots__ = () _fields = {field_names!r} def __new__(_cls, {arg_list}): 'Create new instance of {typename}({arg_list})' return _tuple.__new__(_cls, ({arg_list})) @classmethod def _make(cls, iterable, new=tuple.__new__, len=len): 'Make a new {typename} object from a sequence or iterable' result = new(cls, iterable) if len(result) != {num_fields:d}: raise TypeError('Expected {num_fields:d} arguments, got %d' % len(result)) return result def _replace(_self, **kwds): 'Return a new {typename} object replacing specified fields with new values' result = _self._make(map(kwds.pop, {field_names!r}, _self)) if kwds: raise ValueError('Got unexpected field names: %r' % list(kwds)) return result 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) {field_defs}
举一个例子:比如
Person = namedtuple('Person', 'name, age')
这个代码创建了Person类,其中包括name、age字段,则上面的模板对应的字段如下- typename: 'Person'
- arg_list: name, age
- field_names: 其中
!r
表示调用repr()
。相当于repr(("name", "age"))得到('name', 'age') - num_fields:
len(field_names)
=len(['name', 'age'])
为2 - repr_fmt: (name, age)
- field_defs: 后面介绍
可以看到
namedtuple
继承于tuple
,这样就能像tuple那样作为可迭代对象。 在__new__
函数中调用tuple.__new__
返回tuple实例,在__new__
函数中第一个参数是类本身,余下的参数与__init__
方法一样,返回的实例作为__init__
方法的第一个参数self
-
那像
ss.name
这样的取值操作是怎么实现的呢,还是使用模板,不过是另一个模板_field_template
,定义了property,其中_property从from builtins import property as _property
导入{name} = _property(_itemgetter({index:d}), doc='Alias for field number {index:d}')
上面这个在模板被填充后变为:
name = _property(_itemgetter(0), doc='Alias for field number 0') age = _property(_itemgetter(1), doc='Alias for field number 1')
property
类原型为property(fget=None, fset=None, fdel=None, doc=None)
,通过itemgetter
定义了fget
,其他的方法都没定义,这样就只能是一个只读字段。itemgetter(0)
返回一个获取第1个元素的选择器。比如下面:>>> from operator import itemgetter >>> get = itemgetter(0) >>> get([1,2,3]) 1
因此当我们调用
ss.name
时,实际是运行为ss[0]
-
需要将填充好的模板运行获得真实的类
exec(class_definition, namespace)
之后将其从namespace中得到
result = namespace[typename] result._source = class_definition
最终得到result就是我们定制得到的类