python3.7 的dataclass新特性大大简化了定义类对象的代码量,代码简洁明晰。经过使用@dataclass装饰器来修饰类的设计,例如python
from dataclasses import dataclass @dataclass class DataClassCard: rank: str suit: str #生成实例 queen_of_hearts = DataClassCard('Q', 'Hearts') print(queen_of_hearts.rank) print(queen_of_hearts) print(queen_of_hearts == DataClassCard('Q', 'Hearts'))
运行结果数据结构
Q DataClassCard(rank='Q', suit='Hearts') True
而常规的类,按照3.7以前的语法相似于这样ide
class RegularCard def __init__(self, rank, suit): self.rank = rank self.suit = suit
虽然这种写法并无使用更多的代码量,可是咱们很容易看到为了初始化,仅仅只是为了初始化一个对象,rank和suit已经重复了三次。此外,若是你试图使用这个RegularCard类,你会注意到对象的表示不是很具描述性,而且已有的类与新声明的类是没法比较是否相同的。由于每次声明都会使用一个新的内存地址,而“==”不止比较类存储的信息,还比较内存地址是否相同。函数
具体请看下面代码post
queen_of_hearts = RegularCard('Q', 'Hearts') print(queen_of_hearts.rank) print(queen_of_hearts) print(queen_of_hearts == RegularCard('Q', 'Hearts'))
运行结果ui
'Q' <__main__.RegularCard object at 0x7fb6eee35d30> False
dataclass还在底层给咱们作了更多的有用的封装。默认状况下dataclass实现了repr方法,能够很好的提供字符串表示;也是了eq方法,能够作基本的对象比较。而若是RegularCard想实现上面的功能须要写大量的声明,代码量多的吓人spa
class RegularCard(object): def __init__(self, rank, suit): self.rank = rank self.suit = suit def __repr__(self): #能够将类的信息打印出来 return (f'{self.__class__.__name__}' f'(rank={self.rank!r}, suit={self.suit!r})') #你们能够试着将“!r”去掉或者将其中的r改变为s或a,看看输出结果会有什么变化 #conversion character: expected 's', 'r', or 'a' def __eq__(self, other): #能够比较类是否相同(不考虑内存地址) if other.__class__ is not self.__class__: return NotImplemented return (self.rank, self.suit) == (other.rank, other.suit
在本教程中,您将准确了解dataclass类所带来的便利性。除了很好的表示(当使用print时能够在很好的打印出来)和比较(是否相同),你会看到:设计
如何给dataclass对象添加默认的字段(field)code
如何让dataclass容许对象进行排序对象
如何让dataclass表示不可更改数据
dataclass类的替代方案
可能你接触过nametuple,它经常用来创造可读的轻量级数据结构。实际上咱们能够经过nametuple重复创造数据实例:
from collections import namedtuple NamedTupleCard = namedtuple('NamedTupleCard', ['rank', 'suit'])
NamedTupleCard能够作到DataClassCard能过的事情:
queen_of_hearts = NamedTupleCard('Q', 'Hearts') print(queen_of_hearts.rank) print(queen_of_hearts) print(queen_of_hearts == NamedTupleCard('Q', 'Hearts'))
运行结果跟 DataClassCard同样
'Q' NamedTupleCard(rank='Q', suit='Hearts') True
可是nametuple也有一些限制和不足。例如,咱们不能对nametuple实例的属性值进行更改,由于从根本上将nametuple是元组类,是不可更改数据类型。在某些应用中,这多是很棒的功能,但在其余应用场景中,拥有更多灵活性会更好:
card = NamedTupleCard('7', 'Diamonds') card.rank = '9'
因为nametuple不可更改性,运行结果报错以下
AttributeError: can't set attribute
dataclass基础
如今返回到dataclass,咱们要建立一个Position类,包含名字和经纬度信息的地理位置信息类:
from dataclasses import dataclass @dataclass class Position: name: str lon: float lat: float pos= Position('Oslo', 10.8, 59.9) print(pos) print(pos.lat) print(f'{pos.name} is at {pos.lat}°N, {pos.lon}°E')
咱们解读下Position类代码含义。首先使用@dataclass放在Position上方起到装饰器语法做用。
经过@dataclass装饰后的Position,咱们能够给Position增长一些默认的字段,而且声明这些字段的类型。
运行结果以下:
Position(name='Oslo', lon=10.8, lat=59.9) 59.9 Oslo is at 59.9°N, 10.8°E
咱们也可使用相似于nametuple语法的make_dataclass来建立Position类。代码以下
from dataclasses import make_dataclass
pos = make_dataclass('Position', ['name', 'lat', 'lon'])
print(pos)
#打印结果:<class 'types.Position.
dataclass实际上也是普通的python对象,只不过dataclass帮咱们将 init()、 repr()和eq()封装,更简洁的提供给咱们使用。
dataclass类的默认属性值
在dataclass中很方便的给属性值添加默认值
from dataclasses import dataclass @dataclass class Position: name: str lon: float = 0.0 lat: float = 0.0
dataclass默认值设置相似于init()方法
print(Position('Null Island')) print(Position('Greenwich', lat=51.8)) print(Position('Vancouver', -123.1, 49.3))
默认经纬度均为0.运行结果以下
Position(name='Null Island', lon=0.0, lat=0.0) Position(name='Greenwich', lon=0.0, lat=51.8) Position(name='Vancouver', lon=-123.1, lat=49.3)
稍后咱们会讲到默认工厂(default factory),从而为咱们默认值设置提供了更多更复杂的功能。
类型提示(Type Hints)
您可能已经注意到咱们使用类型提示定义的字段:name:str表示名称应该是文本字符串(str类型)。
实际上,在数据类中定义字段时,必须添加某种类型提示。 若是没有类型提示,该字段将不是dataclass类的一部分。 可是,若是您不想向dataclass类添加显式类型,请使用typing.Any:
from dataclasses import dataclass from typing import Any @dataclass class WithoutExplicitTypes: name: Any value: Any = 42
虽然在使用数据类时须要以某种形式添加类型提示,但这些类型在运行时不会强制执行。
withoutexplicittypes = WithoutExplicitTypes(name=38, value='29') print(withoutexplicittypes)
上面的代码运行没有任何问题,运行结果。咱们发现name是任意类型,而values也是任意类型,虽然默认设置为整数42,可是在这里咱们输入的是字符串29,也能正常运行。
WithoutExplicitTypes(name=38, value='29')
添加方法
dataclass类就是普通的python类,因此咱们能够像给类定义方法同样给dataclass类定义方法。
这里咱们定义距离计算方法,为了方便演示,咱们这里假设地球是二维平面,经纬度表明坐标轴中的位置,使用欧几里得方法计算距离便可。
from dataclasses import dataclass @dataclass class Position: name: str lon: float = 0.0 lat: float = 0.0 def distance(self, newpostion): return sqrt((newpostion.lon - self.lon)**2 + (newpostion.lat - self.lat)**2) pos1 = Position('A', 0, 0) pos2 = Position('B', 0.0, 2.0) pos3 = Position('C', 3.0, 4.0) print(pos1.distance(pos2)) print(pos1.distance(pos3))
A点是坐标原点,B点(0, 2), C点(3, 4)。运行结果
2.0 #AB = 2 5.0 #AC = 5
灵活的dataclass
到如今位置,咱们已经了解了dataclass的基本特性,如今咱们接触些dataclass的高级用法,如参数和field()函数。将这二者结合能让咱们更方便的 控制咱们创造的类。
让咱们回到您在本教程开头看到的扑克牌示例,并在咱们处理时添加一个包含一副牌的类:
from dataclasses import dataclass from typing import List @dataclass class PlayingCard: rank: str suit: str @dataclass class Deck: #Deck:一副牌。cards参数传入列表,该列表中含有多个PlayingCard类实例。 cards: List[PlayingCard] queen_of_hearts = PlayingCard('Q', 'Hearts') ace_of_spades = PlayingCard('A', 'Spades') two_cards = Deck([queen_of_hearts, ace_of_spades]) print(two_cards)
上面的two_cards是最简单的一副牌(Deck类),运行结果以下
Deck(cards=[PlayingCard(rank='Q', suit='Hearts'), PlayingCard(rank='A', suit='Spades')])