暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

「Python」工具人之初学collection(二)

将咖啡转化为程序的工具人 2021-09-09
392

许多年以前,工具人也曾是一名追风的少年。可惜,在经历了社会的毒打后,逐渐变成了一个颓废的中年。唯一不变的,就是对篮球的喜爱。

某天公司组织了3V3篮球比赛,胜利者,可以获取五一劳动节加班资格!工具人毫不犹豫地报名参加了~~~加班,多向往的福报!

那工具人的篮球技术怎么样呢?

这里我们使用namedtuple定义了篮球技能的数据结构。

    from collections import namedtuple
    BasketBallTech = namedtuple('BasketBallTech', ['points', 'rebounds', 'assists'])

    我们定义了一个“篮球技能”的tuple类BasketBallTech,该tuple具有三个属性,分别为得分“points”,篮板“rebounds”和助攻“assists”

    namedtuple作为tuple的扩展类型,包含tuple一切特性。除此以外,可以使用属性名称代替下标方式,获取tuple的属性。

    接下来,我们看看工具人比赛表现如何:

      class ToolMan():
      def __init__(self, technique: BasketBallTech):
      self._technique = technique


      def 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 points
        ToolMan get 8 rebounds
        ToolMan get 2 assists

        很明显,工具人这样的内线核心,是球队不可或缺的。但是,我们在定义工具人的时候,没有很好地“暗示”属性的数据类型,很容易造成“失误”。

        比如:

          naive_tool_man = ToolMan(BasketBallTech(10,"8",2))

          为了解决这个问题,我们可以使用typing包的NamedTuple

            from typing import NamedTuple
            class BasketBallTechV2(NamedTuple):
            points: int
            rebounds: int
            assists: int

            这样,当我们出现上述失误的时候

            静态类型检查工具就会提示我们类型出错了。

            namedtuple是个非常简单,而且常用的数据结构,但让工具人困惑的第一个问题:

            他是如何在运行时被定义的呢?

            进入collections包,我们看到namedtuple的核心代码如下:

              # Fill-in the class template
              class_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 _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}
                """

                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: points
                        get: rebounds

                        可以看到operator模块提供的itemgetter函数可以用于获取对象的哪些维的数据,其参数为一些序号而将itemgetter作为property的自定义fget方法,就能获取到实际tuple对应下标的value。

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

                        文章转载自将咖啡转化为程序的工具人,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                        评论