Python 类的 6 种替代方案(1)

一、Python所有方向的学习路线

Python所有方向路线就是把Python常用的技术点做整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。

二、学习软件

工欲善其事必先利其器。学习Python常用的开发软件都在这里了,给大家节省了很多时间。

三、入门学习视频

我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

在以下文章中,我将使用位置数据作为示例。它必须有一个经度,纬度,外加一个可选的地址。在 C 语言中,可以使用 struct 来表示,而在 Java 中,我们只需创建一个类。在 Python,我们则有六种方法可供选择。让我们来探索他们每一个的优点和缺点吧!

普通类

普通类是标准库为组织数据而提供的默认方式。你可以(而且应该!)使用以下示例中的 类型注释 :

from typing import Optional

class Position:

MIN_LATITUDE = -90

MAX_LATITUDE = 90

MIN_LONGITUDE = -180

MAX_LONGITUDE = 180

def init(

self, longitude: float, latitude: float, address: Optional[str] = None

):

self.longitude = longitude

self.latitude = latitude

self.address = address

@property

def latitude(self) -> float:

“”“Getter for latitude.”“”

return self._latitude

@latitude.setter

def latitude(self, latitude: float) -> None:

“”“Setter for latitude.”“”

if not (Position.MIN_LATITUDE <= latitude <= Position.MAX_LATITUDE):

raise ValueError(f"latitude was {latitude}, but has to be in [-90, 90]")

self._latitude = latitude

@property

def longitude(self) -> float:

“”“Getter for longitude.”“”

return self._longitude

@longitude.setter

def longitude(self, longitude: float) -> None:

“”“Setter for longitude.”“”

if not (Position.MIN_LONGITUDE <= longitude <= Position.MAX_LONGITUDE):

raise ValueError(f"longitude was {longitude}, but has to be in [-180, 180]")

self._longitude = longitude

pos1 = Position(49.0127913, 8.4231381, “Parkstraße 17”)

pos2 = Position(42.1238762, 9.1649964)

def get_distance(p1: Position, p2: Position) -> float:

pass

你可以看到我们需要编写一个构造器方法 __init__ 。构造器的代码在大部分情况下是简单的,尽管有一些例外。

你可以看到我们在代码中使用了位置参数或关键字参数。如果你在构造器中给某一变量定义了一个默认值,那么在创建类的实例的时候可以不给这个变量赋值。可以参考 pos2 ,其中的 address 变量并没有在构造的时候赋值。

你也可以看出这个get_distance 方法的注解非常的清晰,方法本身就很好的表明了它的意义。

由于所有的编辑器都需要支持普通类,因此它的工具支持性是很有保证的。并且能够在调用的时候获取全部的有用信息。

1. 元组

元组是一种基本的 Python 数据类型。它的内存占用很低,因此我们可以通过索引非常快的寻址到所需的元素。元组的问题则是我们无法获知成员属性的名称,我们不得不记住每一个索引代表队属性。元组总是不可修改的。

‘’’

遇到问题没人解答?小编创建了一个Python学习交流QQ群:778463939

寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!

‘’’

from typing import Tuple, Optional

pos1 = (49.0127913, 8.4231381, “Parkstraße 17”)

pos2 = (42.1238762, 9.1649964, None)

def get_distance(p1: Tuple[float, float, Optional[str]],

p2: Tuple[float, float, Optional[str]]) -> float:

pass

get_distance 方法的注解看起来非常的杂乱。开发者需要知道的信息是 p1 表示的是地点,而非这个地点信息包含着两个浮点数以及一个可选的字符串——这是编辑器需要做的工作。

编辑器的支持程度取决于你注解的透彻性。在上面的例子中,你也可以只写 Tuple 而省略掉指出这个元组所包含的内容。由于人们大多是比较懒惰的,我认为这里的编辑器支持做的不是很好。这不是编辑器的错,但是它因此经常无法提供较好的代码提示支持。

2. 字典

字典是 Python 的基本数据类型,并且可能是 Python 中最常见的传递数据的载体。与元组相比,字典由于要保存属性的名称,它的内存占用会大一些,但是这仍是可以接受的。通过索引来获取数据 很快 。字典总是可以修改的,不过有一个第三方的库 frozendict 可以解决字典可以被随意修改的问题。

from typing import Any, Dict

pos1 = {“longitude”: 49.0127913,

“latitude”: 8.4231381,

“address”: “Parkstraße 17”}

pos2 = {“longitude”: 42.1238762,

“latitude”: 9.1649964,

“address”: None}

def get_distance(p1: Dict[str, Any],

p2: Dict[str, Any]) -> float:

pass

在实际中,注解确实很糟糕。通常来说几乎没有字典的注解,在大部分情况下的注解会是 Dict[str, Any] 。

TypedDict ( PEP 589 ) 自从 Python 3.8 一直存在,但是我从没在大型的项目中见到这样的写法。 TypedDict 是一个杀手级功能 ,但是这无关大多数的项目,我们希望在旧有的 Python 版本中也获得此功能支持。

基于上述的原因,字典的编辑器支持效果甚至比元组更差。

3. 命名元组

命名元组( NamedTuples ) 在 Python 2.6 中被加入,索引此数据结构已经存在很久了。命名元组事实上也是元组,但是他们会有一个名称以及一个构造器,用来接受关键字参数:

‘’’

遇到问题没人解答?小编创建了一个Python学习交流QQ群:778463939

寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!

‘’’

from collections import namedtuple

attribute_names = [“longitude”, “latitude”, “address”]

Position = namedtuple(“Position”, attribute_names, defaults=(None,))

pos1 = Position(49.0127913, 8.4231381, “Parkstraße 17”)

pos2 = Position(42.1238762, 9.1649964)

def get_distance(p1: Position, p2: Position) -> float:

pass

命名元组解决了类型声明注解难以阅读的问题。因此,它也解决了我上文中提到的编辑器支持不完全的问题。

有趣的是 NamedTuples 是不能感知到类型的:

from collections import namedtuple

Coordinates = namedtuple(“Coordinates”, [“x”, “y”])

BMI = namedtuple(“BMI”, [“weight”, “size”])

a = Coordinates(60, 170)

b = BMI(60, 170)

a

Coordinates(x=60, y=170)

b

BMI(weight=60, size=170)

a == b

True

4. attrs

attrs 是一个第三方的库,用来减少一些重复模板代码的编写。开发者可以在类上面添加一个 @attrs.s 装饰器来引入。属性则可以使用一个 attr.ib() 方法来赋值:

‘’’

遇到问题没人解答?小编创建了一个Python学习交流QQ群:778463939

寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!

‘’’

from typing import Optional

import attr

@attr.s

class Position:

longitude: float = attr.ib()

latitude: float = attr.ib()

address: Optional[str] = attr.ib(default=None)

@longitude.validator

def check_long(self, attribute, v):

if not (-180 <= v <= 180):

raise ValueError(f"Longitude was {v}, but must be in [-180, +180]")

@latitude.validator

def check_lat(self, attribute, v):

if not (-90 <= v <= 90):

raise ValueError(f"Latitude was {v}, but must be in [-90, +90]")

pos1 = Position(49.0127913, 8.4231381, “Parkstraße 17”)

pos2 = Position(42.1238762, 9.1649964)

def get_distance(p1: Position, p2: Position) -> float:

pass

通过把装饰器改成 @attr.s(frozen=True)来使这个类变得不可修改。

你也可以在构造器入参的时候自动执行代码。这被称为是 “转换”。

@attr.s

… class C(object):

… x = attr.ib(converter=int)

o = C(“1”)

o.x

Visual Studio Code 中对类型注解有很多的插件可以使用。

5. Dataclass

Dataclasses 在 PEP 557 中被加入 Python 3.7。它与 attrs 类似,但是被收录于标准库中。一个很重要的点是 dataclass 就是普通的类, 不过是其中保存大量的数据而已。

与 attrs 不同的是,dataclass 使用类型注解而非 attr.ib() 这样的注解。我认为这样大大提高了可读性。另外,由于现在对属性有了注解,编辑器的支持效果也更好了。

你可以很容易的利用装饰器 @dataclass(frozen=True) 使 dataclass 变成不可修改的——这与 attrs 类似。

from typing import Optional

from dataclasses import dataclass

@dataclass

class Position:

longitude: float

latitude: float

address: Optional[str] = None

pos1 = Position(49.0127913, 8.4231381, “Parkstraße 17”)

pos2 = Position(42.1238762, 9.1649964, None)

def get_distance(p1: Position, p2: Position) -> float:

pass

这里我少说的一部分是属性的验证。可以在构造器中使用__post_init__(self)来实现:

‘’’

遇到问题没人解答?小编创建了一个Python学习交流QQ群:778463939

寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!

‘’’

def post_init(self):

if not (-180 <= self.longitude <= 180):

v = self.longitude

raise ValueError(f"Longitude was {v}, but must be in [-180, +180]")

if not (-90 <= self.latitude <= 90):

v = self.latitude

raise ValueError(f"Latitude was {v}, but must be in [-90, +90]")

你也可以将 dataclass 和属性一起使用:

@dataclass

class Position:

longitude: float

latitude: float

address: Optional[str] = None

@property

def latitude(self) -> float:

“”“Getter for latitude.”“”

return self._latitude

@latitude.setter

def latitude(self, latitude: float) -> None:

“”“Setter for latitude.”“”

if not (-90 <= latitude <= 90):

raise ValueError(f"latitude was {latitude}, but has to be in [-90, 90]")

self._latitude = latitude

@property

def longitude(self) -> float:

“”“Getter for longitude.”“”

return self._longitude

@longitude.setter

def longitude(self, longitude: float) -> None:

“”“Setter for longitude.”“”

if not (-180 <= longitude <= 180):

raise ValueError(f"longitude was {longitude}, but has to be in [-180, 180]")

self._longitude = longitude

但是,我不太喜欢这种超级冗长且丢失了许多 dataclass 独有魅力的手段。 如果你需要类型未涵盖的验证,请使用 Pydantic。

6. Pydantic

Pydantic 是一个专注于数据各实验组和设置管理的第三方库。要使用它,你可以继承自 pydantic.BaseModel 或者创建一个 Pydantic 的 dataclass:

from typing import Optional

from pydantic import validator

from pydantic.dataclasses import dataclass

@dataclass(frozen=True)

class Position:

longitude: float

latitude: float

address: Optional[str] = None

@validator(“longitude”)

def longitude_value_range(cls, v):

if not (-180 <= v <= 180):

raise ValueError(f"Longitude was {v}, but must be in [-180, +180]")

return v

@validator(“latitude”)

def latitude_value_range(cls, v):

if not (-90 <= v <= 90):

raise ValueError(f"Latitude was {v}, but must be in [-90, +90]")

return v

pos1 = Position(49.0127913, 8.4231381, “Parkstraße 17”)

pos2 = Position(longitude=42.1238762, latitude=9.1649964)

def get_distance(p1: Position, p2: Position) -> float:

pass

乍一看,这与标准的 @dataclass 相同,只是从 Pydantic 获得了 dataclass 装饰器。

可变性和散列性

我不太会自觉地考虑可变性,但是在很多情况下,我希望我的类是不变的。最大的例外是数据库模型,但它们本身就是自洽的。

可以选择将类标记为冻结以使其对象不可变,这非常不错。

为一个可变对象实现 hash 是有问题的,因为当对象改变时哈希值可能会改变。这意味着如果对象在字典中,则字典将需要知道对象的哈希值已更改,并将其存储在其他位置。因此,默认情况下,dataclass 和 Pydantic 都不对可变类进行散列,因为他们有 unsafe_hash 。

默认字符串表示

拥有合理的字符串表示形式非常有帮助(例如,用于日志记录)。老实说:很多人都在进行 print 调试。

如果我们打印上面例子中的 pos1 ,下面是我们能得到的。为了方便阅读已经添加了换行和缩进。原始的输出是在一行内的:

‘’’

遇到问题没人解答?小编创建了一个Python学习交流QQ群:778463939

寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!

‘’’

print(pos1)

Plain class : <main.Position object at 0x7f1562750640>

(1)Python所有方向的学习路线(新版)

这是我花了几天的时间去把Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。

最近我才对这些路线做了一下新的更新,知识体系更全面了。

在这里插入图片描述

(2)Python学习视频

包含了Python入门、爬虫、数据分析和web开发的学习视频,总共100多个,虽然没有那么全面,但是对于入门来说是没问题的,学完这些之后,你可以按照我上面的学习路线去网上找其他的知识资源进行进阶。

在这里插入图片描述

(3)100多个练手项目

我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了,只是里面的项目比较多,水平也是参差不齐,大家可以挑自己能做的项目去练练。

在这里插入图片描述

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值