不可变的具名元组 namedtuple ? @ FnEsc | 2021-07-06T19:01:19+08:00 | 3 分钟阅读

collections.namedtuple 是一个工厂函数,它可以用来构建一个带字段名的元组和一个有名字的类。

但如果希望更改属性呢?

namedtuple

介绍

namedtuple 构建的类实例所消耗的内存跟元组是一样的,因为字段名都存在对应的类里面。甚至,namedtuple 实例跟普通的对象实例还要小一些,因为 Python 不会用 __dict__ 来存放这些实例的属性。

namedtuple 定义和使用

>>> import collections
>>> City = collections.namedtuple("City", "name country population cordinates")
>>> tokyo = City("Tokyo", "JP", 36.933, (35.689722, 139.691667))
>>> tokyo.cordinates
(35.689722, 139.691667)
>>> tokyo[1]
'JP'
>>> tokyo[0]
'Tokyo'
>>> tokyo[1], tokyo[2]
('JP', 36.933)
>>> 

可以看到,

  • 声明一个具名元组需要两个参数,第一个是类名,第二是类的各字段名字(这里是 "name country population cordinates") 。
  • 实例化一个具名元组,字段存储的数据应当是一串参数的形式传入到该构造函数中,明确的说,是单一的可迭代对象(这里是 "Tokyo", "JP", 36.933, (35.689722, 139.691667) 四个参数)。

声明具名元组的时候,各字段名字可以是字段串组成的可迭代对象,或者是由空格分割的字符串。

namedtuple 一些属性和方法

展示一些比较有用的方法和属性(接上示例):

>>> City._fields
('name', 'country', 'population', 'cordinates')  # 如名,包含所有字段名称的元组
>>> data = ("Shenzhen", "CN", 10.12, (12.34, 56.78))  # 利用单一的可迭代对象去构造 City 具名元组
>>> sz = City._make(data)  # 参数为单一的可迭代对象,构造类实例,等价与 City(*data)
>>> sz
City(name='Shenzhen', country='CN', population=10.12, cordinates=(12.34, 56.78))  # 友好的字符串表示形式 __repr__
>>> sz._asdict()  # 把具名元组以字典形式返回
{'name': 'Shenzhen', 'country': 'CN', 'population': 10.12, 'cordinates': (12.34, 56.78)}  # 这里使用 python 3.8.2 官方文档表示返回为 dict 而非 OrderedDict

可变对象:类工厂函数

record_factory 可变对象的类

正常编写一个带多属性的类可能像下面这样写?

class Dog:
    def __init__(self, name, weight, owner):
        self.name = name
        self.weight = weight
        self.owner = owner

这样其实太不友好了。我们参考 collections.namedtuple 创建一个 record_factory 函数,即时创建简单的类。

record_factory 简单类工厂函数

def record_factory(cls_name, field_names):
    try:
        field_names = field_names.replace(",", " ").split()
    except AttributeError:
        pass    # “鸭子类型”,参考 namedtuple ,如果不能 replace/split 方法,则假定本身就是标识符组成的序列
    field_names = tuple(field_names)

    def __init__(self, *agrs, **kwargs):
        attrs = dict(zip(self.__slots__, args))  # __slots__ 阻止在实例化类时为实例分配 dict(节省内存),自动带上了 get/set 方法,但同时也不能再增加新的变量
        attrs.update(kwargs)
        for name, value in attrs.items():
            setattr(self, name, value)
    
    def __iter__(self):  # 把实例变成可迭代的对象,按照 __slots__ 设定的顺序产出值
        for name in self.__slots__:
            yield getattr(self, name)
    
    def __repr__(self):  # 生成友好的字符串表示形式 
        values = ",".join( "{}={!r}".format(*r) for i in zip(self.__slots__, self) )
        return "{}({})".format(self.__class__.__name__, values)
    
    cls_attrs = dict(__slots__ = field_names,
                     __init__  = __init__,
                     __iter__  = __iter__,
                     __repr__  = __repr__
                )
    
    return type(cls_name, (object, ), cls_attrs)

__slots__ 可以详细再参考官方文档,看看纯 python 的实现方式,具有更快的属性访问速度,减少内存消耗的优点。

注意,这里最后是调用了 type 的构造方法,构造一个新类,然后将其返回。通常 type 是像函数那样的使用,但也需要知道,type 同时也是一个类。

当成类使用时候,传入三个参数可以新建一个类:namebasesdict,详细可参考官方文档。

record_factory 使用方法

>>> Dog = record_factory("Dog", "name weight owner")
>>> rex = Dog("Rex", 30, "Bob")  # Bob 有一只名为 Rex 的狗
>>> name1, weight1, _ = rex
>>> name1, weight1
('Rex', 30)
>>> "{2}'s dog weighs {1} kg".format(*rex)
"Bob's dog weighs 30 kg"
>>> rex.weight = 32  # 注意这里是“修改”了“具名元组类”
>>> rex
Dog(name='Rex', weight=32, owner='Bob')
>>> Dog.__mro__
(<class 'factories.Dog'>, <class 'object'>)  # 类继承 object,和工厂函数没有关系

这时候,该工厂函数就可以作为工具来使用,当希望创建可变的具名“元组”时候,可以使用该简易类了。

我的一个使用情景是,在对数据的 select 返回时,作一些简单操作,利用了 _fields 属性等。

© 2021 FnEsc Hugo Site

Powered by Hugo with theme Dream.

avatar

FnEsc 的博客一边担心未来,一边浪费现在。

关于我

FnEsc 的 💜 博客

自从 2021.07 开始搭建该 hugo 博客,作为记录一些生活/技术上的小笔记

2020届应届生,毕业与佛山科学技术学院计算机科学与技术专业

目前职业是 全栈开发程序🐶 打杂工具人

作为传统行业外企电商的 965,使我并没有很勤奋卷

不想那么相关工作为 SAP Fiori 应用开发和维护。

目前主要的技术栈是:

  • Python Web 端架构
  • Django / Odoo 开发

接下来可能想学习的方向是:

  • Python 进阶(流畅的 python)
  • MySQL 知识巩固(MySQL 实战 45 讲)
  • Go 开始学习(Go 语言核心 36 讲)
  • 其他架构/算法尝试学习
社交链接