55

[译] Python 3.7 中 dataclass 的终极指南(一)

 5 years ago
source link: https://mp.weixin.qq.com/s/Rgth9Av3IK9a6wDP-NWqTQ?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

标题:The Ultimate Guide to Data Classes in Python 3.7

原文链接https://realpython.com/python-data-classes/

作者:Geir Arne Hjelle

译者:大邓

python3.7 的dataclass新特性大大简化了定义类对象的代码量,代码简洁明晰。通过使用 @dataclass 装饰器来修饰类的设计,例如

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之前的语法类似于这样

class RegularCard
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

虽然这种写法并没有使用更多的代码量,但是我们很容易看到为了初始化,仅仅只是为了初始化一个对象,rank和suit已经重复了三次。此外,如果你试图使用这个RegularCard类,你会注意到对象的表示不是很具描述性,并且已有的类与新声明的类是无法比较是否相同的。 因为每次声明都会使用一个新的内存地址,而“==”不止比较类存储的信息,还比较内存地址是否相同。

具体请看下面代码

queen_of_hearts = RegularCard('Q', 'Hearts')
print(queen_of_hearts.rank)
print(queen_of_hearts)
print(queen_of_hearts == RegularCard('Q', 'Hearts'))

运行结果

'Q'
<__main__.RegularCard object at 0x7fb6eee35d30>
False  

dataclass还在底层给我们做了更多的有用的封装。默认情况下dataclass实现了 __repr__ 方法,可以很好的提供字符串表示;也是了 __eq__ 方法,可以做基本的对象比较。而如果RegularCard想实现上面的功能需要写大量的声明,代码量多的吓人

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)

  • 如何让dataclass允许对象进行排序

  • 如何让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')])

往期文章

100G Python学习资料:从入门到精通! 免费下载

上百G文本数据集等你来认领|免费领取

哈工大国际人工智能暑期学校招生通知

2017年度15个最好的数据科学领域Python库

推荐系统与协同过滤、奇异值分解

机器学习之使用逻辑回归识别图片中的数字

应用PCA降维加速模型训练

使用sklearn做自然语言处理-1

使用sklearn做自然语言处理-2

机器学习|八大步骤解决90%的NLP问题     

Python圈中的符号计算库-Sympy

Python中处理日期时间库的使用方法  

如何从文本中提取特征信息?  

视频讲解】Scrapy递归抓取简书用户信息

美团商家信息采集神器

用chardect库解决网页乱码问题

昨日财报

赞赏、点赞、转发、AD支持都是对大邓的认可和支持,希望大家在阅读后顺便帮大邓转发一下。 额,昨天仅有0.14RMB收入!

NRJfAnf.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK