内容简介:命名的元组实例没有每个实例的字典,因此它们是轻量级的,并且不需要比常规元组更多的内存。假如想计算两个点之间的距离根据定义:
namedtuple
是一个简化 tuple
操作的工厂函数,对于普通元组我们在访问上只能通过游标的访问,在表现力上有时候比不上对象。
命名的元组实例没有每个实例的字典,因此它们是轻量级的,并且不需要比常规元组更多的内存。
假如想计算两个点之间的距离根据定义:
需要两个点的 x、y 坐标,我们可以直接使用元组表示 p1 和 p2 点
>>> import math >>> >>> p1, p2 = (1, 2), (2, 3) >>> s = math.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2) >>> >>> print(s) 1.4142135623730951 >>>
对于 p1 点的 x 坐标使用 p1[0] 表示,对阅读上有一定的困扰,如果可以使用 p1.x
就语义清晰了。
这个场景就是 namedtuple
的典型应用,让字段具有名字,使用 namedtuple
重写上面例子
>>> import collections >>> import math >>> >>> Point = collections.namedtuple('Point', ['x', 'y']) >>> p1, p2 = Point(1, 2), Point(2, 3) >>> >>> s = math.sqrt((p1.x - p2.x)**2 + (p1.y - p2.y)**2) >>> >>> print(s) 1.4142135623730951 >>>
好奇宝宝肯定就会想知道 namedtuple
是如何让字段具有名字的,先看看函数的签名
namedtuple(typename, field_names, *, rename=False, defaults=None,module=None)
第一个和第二参数前面已经使用过了, typename
就是新命名元组的名字,我们最经常的就是模仿的类,所以会使用类的定义风格。 field_names
参数用于定义字段的名字,除了上面使用 ['x', 'y']
还可以使用 "x y"
或者 "x, y"
,定义方法选择自己喜欢的就好。
rename
参数默认是 False
,顾名思义就是重命名字段名字,假如我们使用了非法的变量名(比如关键字等)会被重命名成别的名字。
[!DANGER]
这种改变定义的行为是最好不要做,除非你能保证任何人知道这个行为。
defaults
参数可以是 None
或者一个可迭代的值,根据具有默认值的字段必须在没有初始值的后面,所以 defaults
提供的默认值都是最右匹配。
>>> from collections import namedtuple >>> >>> Point = namedtuple('Point', "x y z", defaults=[2, 3]) >>> p1 = Point(1) >>> >>> print(p1) Point(x=1, y=2, z=3) >>>
如果定义了 module
,则将命名元组的 __module__
属性设置为该值。
... if isinstance(field_names, str): field_names = field_names.replace(',', ' ').split() field_names = list(map(str, field_names)) typename = _sys.intern(str(typename)) ...
进入函数的第一步先对两个基本的参数 typename
和 field_names
进行处理。
如果 field_names
是一个字符串就 replace 把 ,
转化成空格,再 split 成标准的 list。 list(map(str, field_names))
保证了 field_names
的每个值都是 str 类型。
_sys.intern
把 typename 注册到全局中,可以加快对字符串的寻找。
... if rename: seen = set() for index, name in enumerate(field_names): if (not name.isidentifier() or _iskeyword(name) or name.startswith('_') or name in seen): field_names[index] = f'_{index}' seen.add(name) ...
对于设置了 rename=True
会对不合法的 field_name 重新命名,从代码中可以看出重新命名的规则是:如果不合法,判断是不是 关键字 、是不是以 下划线 开头,是不是 已经存在 ,如果符合其中一项就会对用 _{当前的 index}
变量重新命名。
... 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}') 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) ...
接下来对输入的 typename 和 field_names 经检查了一下参数,仍是使用上面的三个规则,确保 typename 和 field_names 中的元素是合法的字符串。
... 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))))) ...
如果设置了 defaults 参数,要最右匹配到 field_names。先使用了 zip
函数,把 reversed
后的 field_names 和 defaults 组合成元组的 list
>>> field_names = ['x', 'y', 'z'] >>> defaults = [2, 3] >>> >>> print(list(zip(reversed(field_names), reversed(defaults)))) [('z', 3), ('y', 2)] >>>
最后在使用 dict(reversed(...))
转化成 dict 类型。
... # 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__ _dict, _tuple, _len, _map, _zip = dict, tuple, len, map, zip # 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) __new__ = namespace['__new__'] __new__.__doc__ = f'Create new instance of {typename}({arg_list})' if defaults is not None: __new__.__defaults__ = defaults ...
这部分动态设置参数的过程,重点关注 exec(s, namespace)
,s 是 __new__
方法的定义,其中的 arg_list
是我们设置的属性名字会转换成 x, y, x
这种形式,填充的 s 中。namespace 则是 exec 过程中可使用的变量,这里传入了 tuple_new = tuple.__new__
用于创建一个新的 tuple。
... @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 dict which maps field names to their values.' return _dict(_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}.{method.__name__}' ...
接着定义了一些列的方法,这些方法最后都是用于生成 namedtuple 后所拥有的方法,根据简单的注释可以很容易知道他们的用途
... # 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, '_field_defaults': field_defaults, # alternate spelling for backward compatiblity '_fields_defaults': field_defaults, '__new__': __new__, '_make': _make, '_replace': _replace, '__repr__': __repr__, '_asdict': _asdict, '__getnewargs__': __getnewargs__, } # _tuplegetter = lambda index, doc: property(_itemgetter(index), doc=doc) for index, name in enumerate(field_names): doc = _sys.intern(f'Alias for field number {index}') class_namespace[name] = _tuplegetter(index, doc) result = type(typename, (tuple,), class_namespace) ...
定义 class_namespace
传入上面定义好一系列方法,最后使用 type
创建出一个新的 class。
[!NOTE]
Python 所有的东西都是 type 这个函数创建出来的,包括 type 本身,更多 type 相关信息参考
https://docs.python.org/3/library/functions.html#type... # 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 ...
最后需要把 module 属性设置回 result 的 __module__
中,这些信息会在 pickle 会被用到。
总结一下,namedtuple 创建过程大体分成三个部分:
__new__ type
其实在不久之前,namedtuple 还是直接使用字符串模板生成,现在这种实现方法更优雅了。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- CPython 标准库源码分析 collections.Counter
- Golang标准库源码阅读 - net/http.Transport
- Go 标准库源码学习(一):详解短小精悍的 Once
- Golang 源码剖析:fmt 标准库 --- Print* 是怎么样输出的?
- 中兴通讯助力DevOps标准发布,喜获标准工作组成员授牌
- CODING 受邀参与 DevOps 标准体系之系统和工具 & 技术运营标准技术专家研讨会
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
PHP for the World Wide Web, Second Edition (Visual QuickStart Gu
Larry Ullman / Peachpit Press / 2004-02-02 / USD 29.99
So you know HTML, even JavaScript, but the idea of learning an actual programming language like PHP terrifies you? Well, stop quaking and get going with this easy task-based guide! Aimed at beginning ......一起来看看 《PHP for the World Wide Web, Second Edition (Visual QuickStart Gu》 这本书的介绍吧!