第五章属于第二版的新章了,讲的是数据类,不知道这么翻译对不对,但是意思就是只是通过字段来保存数据的这种类,类似 c 的结构体这样子。先看一个简单的数据类的构建:
class Coordinate:
def __init__(self, lat, lon):
self.lat = lat
self.lon = lon
只有一些基本特征:
>>> from coordinates import Coordinate
>>> moscow = Coordinate(55.76, 37.62)
>>> moscow
<coordinates.Coordinate object at 0x107142f10>
>>> location = Coordinate(55.76, 37.62)
>>> location == moscow
False
>>> (location.lat, location.lon) == (moscow.lat, moscow.lon)
True
如果用 namedtuple 这个类来创建类:
>>> from collections import namedtuple
>>> Coordinate = namedtuple('Coordinate', 'lat lon')
>>> issubclass(Coordinate, tuple)
True
>>> moscow = Coordinate(55.756, 37.617)
>>> moscow
Coordinate(lat=55.756, lon=37.617)
>>> moscow == Coordinate(lat=55.756, lon=37.617)
True
或者新的 Typing.NamedTuple,需要标注类型:
>>> import typing
>>> Coordinate = typing.NamedTuple('Coordinate',
... [('lat', float), ('lon', float)])
>>> issubclass(Coordinate, tuple)
True
>>> typing.get_type_hints(Coordinate)
{'lat': <class 'float'>, 'lon': <class 'float'>}
python 3.6 也引进了新的语法,当创建类的时候:
from typing import NamedTuple
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}'
这里的 NamedTuple 是作为元类 而不是 继承的关系:
>>> issubclass(Coordinate, typing.NamedTuple)
False
>>> issubclass(Coordinate, tuple)
True
这里元类的作用类似类装饰器,也可以这么写:
from dataclasses import dataclass
@dataclass(frozen=True)
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}'
这些数据类构造器有一些主要特性需要一一说明:
- 两种 namedtuple 构造出来的实例是不可变的,但是默认的类装饰器创建的类是可变的,如果frozen=True 那么实例里面字段的值就是不可变的了
- 只有 typing.NamedTuple 和 dataclass 支持类的创建语法,并且可以增加自定义的方法以及注解
- 两种 namedtuple 都有 _.asdict 构造成字典对象,dataclasses 则是 dataclasses.asdict
- 获取字段名字和默认值时,namedtuple 通过 _.fields 和 _.fields_defaults 类属性来完成,dataclasses 则是通过类里面的 fields 方法,他会返回 名字和 默认值
- 虽然 typing named tuple 和 dataclass 都可以通过 __annotations__ 来获取字段类型,但是 typing namedtuple 应该使用 typing.get_type_hints()
- 如果像新建一个部分字段相同,部分字段被更新的实例,named tuple 调用 ._replace(**kwargs) ,dataclass 则