Python 类的 6 种替代方案

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 Tuples : (49.0127913, 8.4231381, ‘Parkstraße 17’)

2 Dicts : {‘longitude’: 49.0127913,

‘latitude’: 8.4231381,

‘address’: ‘Parkstraße 17’}

3 NamedTuple: Position(longitude=49.0127913,

latitude=8.4231381,

address=‘Parkstraße 17’)

4 attrs : Position(longitude=49.0127913,

latitude=8.4231381,

address=‘Parkstraße 17’)

5 dataclass : Position(longitude=49.0127913,

latitude=8.4231381,

address=‘Parkstraße 17’)

可以看到从普通类创建的对象的字符串表示形式是无用的。元组看起来更好,但是它们没有指出哪个索引代表哪个属性。其余所有表示形式都很棒。它们很容易理解,甚至可以用来重新创建对象!

数据验证

现在我们已经了解了如何为普通类、attrs、dataclass 和 Pydantic 实现数据验证。但我们还并不清楚错误消息的样子。

接下来,我将新建一个 Position(1234, 567) ,里面的经度和纬度都是不正确的。下面是不同的数据结构触发的错误信息:

Plain Class

ValueError: Longitude was 11111, but has to be in [-180, 180]

4: attr

ValueError: Longitude was 1234, but must be in [-180, +180]

5: dataclasses

(same as plain classes is possible)

6: Pydantic

pydantic.error_wrappers.ValidationError: 2 validation errors for Position

longitude

Longitude was 1234.0, but must be in [-180, +180] (type=value_error)

latitude

Latitude was 567.0, but must be in [-90, +90] (type=value_error)

我要指出的是这一点:Pydantic 非常清楚地为我们提供了所有错误。 普通的类和属性只会给我们返回第一个错误。

JSON 序列化

JSON 是在网络上交换数据的方式。GitLab API 也不例外。假设我们要拥有可以序列化为 JSON 的 Python 对象,以[获取单个合并分支请求]( docs.gitlab.com/ee/api/merg… 在 Pydantic 中,就这么简单(删除了许多属性以提高可读性):

from pydantic import BaseModel

class GitlabUser(BaseModel):

id: int

username: str

class GitlabMr(BaseModel):

id: int

squash: bool

web_url: str

title: str

author: GitlabUser

mr = GitlabMr(

id=1,

squash=True,

web_url=“http://foo”,

title=“title”,

author=GitlabUser(id=42, username=“Joe”),

)

json_str = mr.json()

print(json_str)

这返回了:

{“id”: 1, “squash”: true, “web_url”: “http://foo”, “title”: “title”, “author”: {“id”: 42, “username”: “Joe”}}

对于 dataclasses 而言, dataclasses.asdict 做了很多工作。然后,字典可以被直接序列化为 JSON。对于 DateTime 或者 小数 对象的结果会很有趣。 attrs 的结果也是 相似的 。

JSON 的反序列化

使用 JSON 字符串对嵌套类进行用户化对于 Pydantic 来说是很容易的。使用上面的示例,可以这么写:

mr = GitlabMr.parse_raw(json_str)

datatclass 的实现则很不优雅。对于 attrs 的反序列化 则看起来好一些,但我猜想它在嵌套结构方面也很困难。而且,当谈到 DateTime 或小数时,我敢肯定,两者都比 Pydantic 出现更多的问题。序列化,反序列化和验证是 Pydantic 的亮点。

内存占用

在 pos1 调用 getsize 方法可得:

Raw float : 8 B (“double”)

Raw string : 1 B per char => 13B

Raw data : 29 B = 8B + 8B + 13B

Float object : 24 B

Str object : 86 B

3 objects : 134 B = 24B + 24B + 86B

Native class : 286 B

#1 Tuple : 198 B

#2 Dict : 366 B

#3 NamedTuple: 198 B

#4 attrs : 286 B

#5 dataclass : 286 B

#6 pydantic : 442 B (the “dataclass” version)

#6 pydantic : 801 B (the “BaseModel” version)

Pydantic 基本模型有相当大的开销,但是你始终必须明白的一点是,你将创建多少个这些对象?假设你有 100 个。它们中的每一个可能比更有效的替代方案多消耗 500B。 那将是 50kB。 引用 Donald Knuth :

“过早的优化是万恶之源。”

如果内存占用出现了问题,那么你也不会从 Pydantic 切换到 dataclass 或 attrs,而是切换到更结构化的内容,例如 NumPy 数组或 pandas 的 DataFrames。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注Python)

的朋友,同时减轻大家的负担。**

[外链图片转存中…(img-iUvShINs-1712496895560)]

[外链图片转存中…(img-LYXOr3ru-1712496895561)]

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注Python)

img
  • 12
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python中的`concurrent.futures.ThreadPoolExecutor`是一个很有用的工具,它能够方便地管理和执行线程池中的任务。然而,有时候我们可能需要找到替代品来满足不同需求或解决一些ThreadPoolExecutor存在的缺点。 1. `concurrent.futures.ProcessPoolExecutor`:`ProcessPoolExecutor`提供了与`ThreadPoolExecutor`类似的功能,但它使用进程而不是线程来执行任务。由于Python的全局解释器锁(GIL)的限制,使用进程来并行执行任务可能会比使用线程更有效。因此,当处理CPU密集型任务时,这个替代品可能是更好的选择。 2. `asyncio`:`asyncio`是Python 3.7及以上版本中引入的标准库,它提供了协程(coroutine)和事件循环(event loop)的支持。与传统的线程池不同,`asyncio`基于单个线程的事件循环和非阻塞IO操作来实现并发。通过使用异步编程的范式,`asyncio`可以实现高效的并发处理。因此,对于高并发网络应用程序,`asyncio`可以作为`ThreadPoolExecutor`的替代品。 3. `ray`:`ray`是一个开源的分布式计算框架,它可用于构建高性能和可伸缩的应用程序。`ray`提供了丰富的并行计算API,包括支持任务并行化的基于Actor模型的编程模型。相比于`ThreadPoolExecutor`,`ray`具有更强大的功能,可以在分布式集群上执行任务。 总的来说,`concurrent.futures.ThreadPoolExecutor`是一个很实用的工具,但在一些特定场景下可能需要找到适合的替代品。`ProcessPoolExecutor`、`asyncio`和`ray`是Python中一些常见的替代方案,可以根据具体需求选择合适的工具来实现并发和并行计算。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值