Python数据类构建器(一)

Python提供几种构建简单类的方式,这些类只是字段的容器,几乎没有额外的功能,这种模式被称为“数据类”(data class)。下面介绍3个可简化数据类构建过程的类构建器。

  • collectons.namedtuple,最简单的构建方式,从Python2.6开始提供
  • typing.NamedTuple,需要为字段添加类型提示,从Python3.5开始提供。class句法在3.6中添加
  • @dataclasses.datacless,与前两种相比,可定制的内容更多,添加了大量选项,可实现更复杂的功能。

有同学可能会觉得Python可以通过类用于保存数据,例如下面通过Coordinate类保存经纬度的属性:

class Coordinate:
    def __init__(self, lat, lon):
        self.lat = lat
        self.lon = lon

beijing = Coordinate(116.405285, 39.904989)
print(beijing.lat, beijing.lon)  # 116.405285 39.904989
beijing1 = Coordinate(116.405285, 39.904989)
print(beijing == beijing1)  # False
print((beijing.lat, beijing.lon) == (beijing1.lat, beijing.lon))  # True

但是如果属性太多的时候,不觉得编写起来很枯燥吗,要知道Python是一门优雅的语言。另外更糟糕的是,上面案例编写的代码并没有给我们提供Python对象都有的基本功能。例如我们比较beijing和beijing1对象,明明数据是一样的,但是却不相等,只能一个个拆开进行比较各个属性。至于为什么对象数据相同却不相等是因为该类继承的是Object,而继承Object的__eq__方法比较的是对象ID。

下面使用namedtuple来构建数据类,namedtuple是一个工厂方法,需要一个指定名称:typename和字段:field_names来构建返回带有命名字段的元组新子类。

from collections import namedtuple

Coordinate = namedtuple("Coordinate", ["lat", "lon"])  # ["lat", "lon"]也可以写成"lat lon"按个人的兴趣来
print(issubclass(Coordinate, tuple))  # True 很明显namedtuple构建的类是tuple的子类
beijing = Coordinate(116.405285, lon=39.904989)
beijing1 = Coordinate(116.405285, lon=39.9049)
print(beijing1 == beijing1)  # True

typing.NamedTuple具有一样的功能,不过他可以为每个字段添加类型注解。

from typing import NamedTuple, get_type_hints

Coordinate = NamedTuple("Coordinate", [('lat', float), ('lon', float)])
print(issubclass(Coordinate, tuple))  # True
print(get_type_hints(Coordinate))  # {'lat': <class 'float'>, 'lon': <class 'float'>}
# 也可以使用映射的方式定义,这种方式可读性更高,使用拆包构建
coordinate_dict = {'lat': float, 'lon': float}
Coordinate1 = NamedTuple("Coordinate1", **coordinate_dict)
print(get_type_hints(Coordinate1))  # {'lat': <class 'float'>, 'lon': <class 'float'>}

NamedTuple也可以在class里面使用,这样可读性更高,而且也方便覆盖方法或添加新方法

class Coordinate(NamedTuple):
    lat: float
    lon: float

    def __str__(self):
        ns = 'N' if self.lat >= 0 else 'S'
        we = 'E' if self.lon >= 0 else 'W'
        return f'{abs(self.lat):.1f}°{ns}, {abs(self.lon):.1f}°{we}'


coordinate = Coordinate(lat=116.405285, lon=39.9049)
print(coordinate)  # 116.4°N, 39.9°E
print(issubclass(Coordinate, tuple)) # True

与NamedTuple一样,dataclass装饰器也能读取变量注解,自动为构建的类生成方法。但是dataclass不依赖继承或元类,个人更喜欢使用dataclass。

from dataclasses import dataclass
@dataclass
class Coordinate:
    lat: float
    lon: float

    def __str__(self):
        ns = 'N' if self.lat >= 0 else 'S'
        we = 'E' if self.lon >= 0 else 'W'
        return f'{abs(self.lat):.1f}°{ns}, {abs(self.lon):.1f}°{we}'

coordinate = Coordinate(lat=116.405285, lon=39.9049)
print(coordinate)  # 116.4°N, 39.9°E

主要功能

 namedtupleNamedTupledataclass
可变实例
class 语句句法
构造字典x._asdict()x._asdict()dataclasses.asdict(x)
获取字段名称x._fieldsx._fields[f.name for f in dataclasses.fields(x)]
获取默认值x._field_defaultsx._field_defaults[f.default for f in dataclasses.fields(x)]
获取字段类型N/Ax.__annotations__x.__annotations__
更改之后创建 新实例x._replace(...)x._replace(...)dataclasses.replace(x, ...)
运⾏时定义新类namedtuple(...)NamedTuple(...)dataclasses.make_dataclass(...)

typing.NamedTuple 和 @dataclass 构建的类有一个 __annotations__ 属性,存放字段的类型提示。然而,不建议直接读取 __annotations__ 属性。推荐使⽤ inspect.get_annotations(MyClass)(Python 3.10 新增) 或 typing.get_type_hints(MyClass)(Python 3.5~3.9)获取类型信息。

  • 可变实例:三个构建类之前的区别在于,collections.namedtuple 和 typing.NamedTuple 构建类是tuple的子类,因此是不可变的。@dataclass默认是可变的类,但是装饰器有个关键字参数frozen,如果指定frozen=True,为构建类赋值,则会抛出异常
  • class 语句句法:只有 typing.NamedTuple 和 dataclass ⽀持常规的 class 语句句法,方便为构建的类添加方法和文档字符串
  • 构造字典:两种具名元组都提供了构造 dict 对象的实例方法 (._asdict),可根据数据类实例的字段构造字典。 dataclasses 模块也提供了构造字典的函数,即 dataclasses.asdict
  • 获取字段名称和默认值:3 个类构建器都支持获取字段名称和可能配置的默认值。对于具名元组类,这些元数据在类属性 ._fields 和 ._fields_defaults 中。对于使用dataclass 装饰器构建的 类,这些元数据使用dataclasses 模块中的 fields 函数获取。fields 函数返回⼀个由 Field 对象构成的元组,Field 对象有几个属性,包括 name 和 default
  • 获取字段类型:typing.NamedTuple 和 @dataclass 定义的类有⼀个 __annotations__ 类属性,值为字段名称到类型的映射。前面说过,不要直接读取 __annotations__ 属性,而要使用typing.get_type_hints 函数
  • 更改之后创建新实例:对于具名元组实例 x,x._replace(**kwargs) 根据指定的关 键字参数替换某些属性的值,返回⼀个新实例。模块级函数 dataclasses.replace(x, **kwargs) 与 dataclass 装饰的类具有相同的作用
  • 运⾏时定义新类:class句法虽然可读性更高,但毕竟还是硬编码的。框架可能需要在运动时动态构建数据类。为此,可以使用默认的函数调用句法,collections.namedtuple 和 typing.NamedTuple 都支持。dataclasses 模块提供的 make_dataclass 函数也是出于这个目的。

 

 

  • 30
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值