许多年以前,工具人也曾是一名追风的少年。可惜,在经历了社会的毒打后,逐渐变成了一个颓废的中年。唯一不变的,就是对篮球的喜爱。
某天公司组织了3V3篮球比赛,胜利者,可以获取五一劳动节加班资格!工具人毫不犹豫地报名参加了~~~加班,多向往的福报!
那工具人的篮球技术怎么样呢?
这里我们使用namedtuple定义了篮球技能的数据结构。
from collections import namedtupleBasketBallTech = namedtuple('BasketBallTech', ['points', 'rebounds', 'assists'])
我们定义了一个“篮球技能”的tuple类BasketBallTech,该tuple具有三个属性,分别为得分“points”,篮板“rebounds”和助攻“assists”
namedtuple作为tuple的扩展类型,包含tuple一切特性。除此以外,可以使用属性名称代替下标方式,获取tuple的属性。
接下来,我们看看工具人比赛表现如何:
class ToolMan():def __init__(self, technique: BasketBallTech):self._technique = techniquedef get_points(self):print('ToolMan get {} points'.format(self._technique.points))def get_rebounds(self):print('ToolMan get {} rebounds'.format(self._technique.rebounds))def get_assists(self):print('ToolMan get {} assists'.format(self._technique.assists))if __name__ == "__main__":# 追风少年工具人naive_tool_man = ToolMan(BasketBallTech(10,8,2))naive_tool_man.get_points()naive_tool_man.get_rebounds()naive_tool_man.get_assists()
输出:
ToolMan get 10 pointsToolMan get 8 reboundsToolMan get 2 assists
很明显,工具人这样的内线核心,是球队不可或缺的。但是,我们在定义工具人的时候,没有很好地“暗示”属性的数据类型,很容易造成“失误”。
比如:
naive_tool_man = ToolMan(BasketBallTech(10,"8",2))
为了解决这个问题,我们可以使用typing包的NamedTuple
from typing import NamedTupleclass BasketBallTechV2(NamedTuple):points: intrebounds: intassists: int
这样,当我们出现上述失误的时候
静态类型检查工具就会提示我们类型出错了。

namedtuple是个非常简单,而且常用的数据结构,但让工具人困惑的第一个问题:
他是如何在运行时被定义的呢?
进入collections包,我们看到namedtuple的核心代码如下:
# Fill-in the class templateclass_definition = _class_template.format(typename = typename,field_names = tuple(field_names),num_fields = len(field_names),arg_list = repr(tuple(field_names)).replace("'", "")[1:-1],repr_fmt = ', '.join(_repr_template.format(name=name)for name in field_names),field_defs = '\n'.join(_field_template.format(index=index, name=name)for index, name in enumerate(field_names)))# Execute the template string in a temporary namespace and support# tracing utilities by setting a value for frame.f_globals['__name__']namespace = dict(__name__='namedtuple_%s' % typename)exec(class_definition, namespace)
这里的_class_template是一个字符串模板
_class_template = """\from builtins import property as _property, tuple as _tuplefrom operator import itemgetter as _itemgetterfrom collections import OrderedDictclass {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}))@classmethoddef _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 resultdef _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 resultdef __repr__(self):'Return a nicely formatted representation string'return self.__class__.__name__ + '({repr_fmt})' % selfdef _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}"""
collections使用我们传入的'BasketBallTech'以及属性数组['points', 'rebounds', 'assists']填充了字符串_class_template
最后通过exec(class_definition, namespace)动态地生成了BasketBallTech类。
工具人困惑的第二个问题:
points等属性,是如何关联到底层tuple(10,8,2)的每个下标成员的呢?
我们注意到,_class_template的最后一行:{field_defs}
这里通过参数:
field_defs = '\n'.join(_field_template.format(index=index, name=name)for index, name in enumerate(field_names))
生成了所有的属性定义,即生成了所有的属性定义,即'points', 'rebounds', 'assists',
其中,_field_template又是一个字符串模板:
_field_template = '''\{name} = _property(_itemgetter({index:d}), doc='Alias for field number {index:d}')'''
最后实际生成的字符串为:
points = _property(_itemgetter(0), doc='Alias for field number 0')rebounds = _property(_itemgetter(1), doc='Alias for field number 1')assists = _property(_itemgetter(2), doc='Alias for field number 2')
这里的核心技巧是itemgetter,我们先举个简单例子来解释下itemgetter的使用方式:
fields = ('points', 'rebounds', 'assists')f = itemgetter(0)print("get: {}".format(f(fields)))f = itemgetter(1)print("get: {}".format(f(fields)))#这样我们就会得到:get: pointsget: rebounds
可以看到operator模块提供的itemgetter函数可以用于获取对象的哪些维的数据,其参数为一些序号。而将itemgetter作为property的自定义fget方法,就能获取到实际tuple对应下标的value。

好了,工具人的下一场篮球比赛就要开始了,大家祝他好运吧~




