Python | 告别这些过时的 Python 库

a416dee5e534e28ab970aa0c7236efd5.gif

b40456ffcd3e2572d1a06abea3157e06.png

!! 每个 Python 版本都会添加新模块,并引入新的更好的处理方式。我们都习惯了使用优秀的旧 Python 库和特定的做事方式,但现在是升级并使用新的和改进的模块及其功能的时候了。

1. pathlib

pathlib绝对是 Python 标准库中最近添加的更大的之一。自 Python 3.4 以来它一直是标准库的一部分,但很多人仍然使用os模块进行文件系统操作。pathlib然而,与旧版本相比有很多优点os.path——虽然os模块以原始字符串格式表示路径,但pathlib使用面向对象的风格,这使得它更具可读性和编写自然:

from pathlib import Path
import os.path

#Old, Unreadable
two_dirs_up = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
#New, Readable
two_dirs_up = Path(__file__).resolve().parent.parent

!! 路径被视为对象而不是字符串的事实也使得创建对象一次然后查找其属性或对其进行操作成为可能。

readme = Path("README.md").resolve()

print(f"Absolute path: {readme.absolute()}")
# Absolute path: /home/elio/some/path/README.md
print(f"File name: {readme.name}")
# File name: README.md
print(f"Path root: {readme.root}")
# Path root: /
print(f"Parent directory: {readme.parent}")
# Parent directory: /home/elio/some/path
print(f"File extension: {readme.suffix}")
# File extension: .md
print(f"Is it absolute: {readme.is_absolute()}")
# Is it absolute: True

!! 不过,我最喜欢的一个功能pathlib是可以使用/( "division" ) 运算符来连接路径。

# Operators:
etc = Path('/etc')

joined = etc / "cron.d" / "anacron"
print(f"Exists? - {joined.exists()}")
# Exists? - True

这使得路径的处理变得如此简单,而且真的是非常实用。话虽如此,重要的是要注意这pathlib只是替代os.path而不是整个os模块。然而,它还包括来自glob模块的功能,因此如果你习惯于与os.path结合使用glob.glob,那么你可以忘记这两个存在。在上面的代码片段中,我们介绍了一些方便的路径操作和对象属性,但pathlib也包括你习惯使用的所有方法 from os.path,例如:

print(f"Working directory: {Path.cwd()}")  # same as os.getcwd()
# Working directory: /home/elio/some/path
Path.mkdir(Path.cwd() / "new_dir", exist_ok=True)  # same as os.makedirs()
print(Path("README.md").resolve())  # same as os.path.abspath()
# /home/elio/some/path/README.md
print(Path.home())  # same as os.path.expanduser()
# /home/elio

2.Secrets

说到os模块,你应该停止使用的另一部分是os.urandom. 相反,你应该使用secrets自 Python 3.6 以来可用的新模块:

# Old:
import os

length = 64

value = os.urandom(length)
print(f"Bytes: {value}")
# Bytes: b'\xfa\xf3...\xf2\x1b\xf5\xb6'
print(f"Hex: {value.hex()}")
# Hex: faf3cc656370e31a938e7...33d9b023c3c24f1bf5

# New:
import secrets

value = secrets.token_bytes(length)
print(f"Bytes: {value}")
# Bytes: b'U\xe9n\x87...\x85>\x04j:\xb0'
value = secrets.token_hex(length)
print(f"Hex: {value}")
# Hex: fb5dd85e7d73f7a08b8e3...4fd9f95beb08d77391

使用os.urandom实际上并不是这里的问题,引入模块的原因 secrets是因为人们使用random模块来生成密码等,即使random模块不生成密码安全令牌。根据文档,random模块不应该用于安全目的。你应该使用secrets或os.urandom,但secrets考虑到它较新并且包含一些用于十六进制标记以及 URL 安全标记的实用程序/便捷方法,该模块绝对是更可取的。

3. Zoneinfo

!! 在 Python 3.9 之前,没有用于时区操作的内置库,所以每个人都在使用pytz,但现在我们有zoneinfo标准库,所以是时候切换了!

from datetime import datetime
import pytz  # pip install pytz
dt = datetime(2023, 1, 4)
nyc = pytz.timezone("Asia/Shanghai")
localized = nyc.localize(dt)
print(f"Datetime: {localized}, Timezone: {localized.tzname()}, TZ Info: {localized.tzinfo}")
# New:
from zoneinfo import ZoneInfo
nyc = ZoneInfo("Asia/Shanghai")
localized = datetime(2023, 1, 4, tzinfo=nyc)
print(f"Datetime: {localized}, Timezone: {localized.tzname()}, TZ Info: {localized.tzinfo}")
Datetime: 2023-01-04 00:00:00+08:00, Timezone: CST, TZ Info: Asia/Shanghai
Datetime: 2023-01-04 00:00:00+08:00, Timezone: CST, TZ Info: Asia/Shanghai

该datetime模块将所有时区操作委托给抽象基类datetime.tzinfo。这个抽象基类需要一个具体的实现 - 在引入这个很可能来自pytz. 现在我们zoneinfo在标准库中有了,我们可以使用它来代替。 但是使用zoneinfo有一个警告 - 它假定系统上有可用的时区数据,在 UNIX 系统上就是这种情况。如果你的系统没有时区数据,那么你应该使用tzdata包,它是由 CPython 核心开发人员维护的第一方库,其中包含 IANA 时区数据库。

扫描二维码关注公众号,回复: 15505242 查看本文章

4. dataclasses

Python 3.7 的一个重要补充是dataclassespackage,它替代了namedtuple。你可能想知道为什么需要更换namedtuple?因此,这些是你应该考虑切换到的一些原因dataclasses:

  • 可以是可变的,

  • 默认提供__repr__, __eq__, __init__,__hash__魔术方法,

  • 允许指定默认值,

  • 支持继承。

此外,数据类还支持__frozen__和__slots__(从 3.10 开始)属性以提供与命名元组相同的特性。切换真的太困难,因为你只需要更改定义:

# Old:
# from collections import namedtuple
from typing import NamedTuple
import sys

User = NamedTuple("User", [("name", str), ("surname", str), ("password", bytes)])

u = User("John", "Doe", b'tfeL+uD...\xd2')
print(f"Size: {sys.getsizeof(u)}")
# Size: 64

# New:
from dataclasses import dataclass

@dataclass()
class User:
    name: str
    surname: str
    password: bytes

u = User("John", "Doe", b'tfeL+uD...\xd2')

print(u)
# User(name='John', surname='Doe', password=b'tfeL+uD...\xd2')

print(f"Size: {sys.getsizeof(u)}, {sys.getsizeof(u) + sys.getsizeof(vars(u))}")
# Size: 48, 152

namedtuple在上面的代码中,我们还包括了大小比较,因为这是和之间较大的差异之一dataclasses。命名元组的大小要小得多,这是由于使用数据类dict来表示属性。至于速度比较,属性的访问时间应该基本相同,或者不够重要,除非你计划创建数百万个实例,比如下面的代码。

import timeit

setup = '''
from typing import NamedTuple
User = NamedTuple("User", [("name", str), ("surname", str), ("password", bytes)])
u = User("John", "Doe", b'')
'''

print(f"Access speed: {min(timeit.repeat('u.name', setup=setup, number=10000000))}")
# Access speed: 0.16838401100540068

setup = '''
from dataclasses import dataclass

@dataclass(slots=True)
class User:
    name: str
    surname: str
    password: bytes

u = User("John", "Doe", b'')
'''

print(f"Access speed: {min(timeit.repeat('u.name', setup=setup, number=10000000))}")
# Access speed: 0.17728697300481144

!! 如果你不想切换并且出于某种原因真的想使用命名元组,那么你至少应该NamedTuple来自typing模块而不是来自collections模块。

# Bad:
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])

# Better:
from typing import NamedTuple
class Point(NamedTuple):
    x: float
    y: float

!! 最后,如果你既不使用namedtuple也不dataclasses考虑直接使用Pydantic。

5. f 格式化字符串

!! Python 包含多种格式化字符串的方法,包括 C 风格格式化、f 字符串、模板字符串或.format函数。不过,其中之一 - f-strings - 格式化的字符串文字 - 只是更好。它们写起来更自然,更具可读性,并且是前面提到的选项中最快的。因此,我认为争论或解释为什么应该使用它们是没有意义的。然而,在某些情况下不能使用 f :

import logging

things = "something happened..."

logger = logging.getLogger(__name__)
logger.error("Message: %s", things)  # Evaluated inside logger method
logger.error(f"Message: {things}")  # Evaluated immediately

在上面的示例中,如果你使用 f-strings,表达式将被立即评估,而使用 C 样式格式,替换将被推迟到实际需要时。这对于消息分组很重要,其中具有相同模板的所有消息都可以记录为一个。这不适用于 f-strings,因为模板在传递给记录器之前会填充数据。此外,有些事情是 f-strings无法做到的。例如在运行时填充模板——即动态格式化——这就是 f-strings 被称为文字字符串格式化的原因。

def func(tpl: str, param1: str, param2: str) -> str:
    return tpl.format(param=param1, param2=param2)

some_template = "First template: {param1}, {param2}"
another_template = "Other template: {param1} and {param2}"

print(func(some_template, "Hello", "World"))
print(func(another_template, "Hello", "Python"))

inputs = ["Hello", "World", "!"]
template = "Here's some dynamic value: {value}"

for value in inputs:
    print(template.format(value=value))

!! 最重要的是,尽可能使用 f 字符串,因为它们更具可读性和更高的性能,但请注意,在某些情况下,其他格式样式仍然是首选和/或必要的。

6.tomllib解析库

TOML 是一种广泛使用的配置格式,对 Python 的工具和生态系统尤为重要,因为它用于pyproject.toml配置文件。到现在为止,你必须使用外部库来管理 TOML 文件,但是从 Python 3.11 开始,将会有tomllib一个基于package.xml 的内置库命名tomli。所以,一旦你切换到 Python 3.11,你应该养成使用import tomllib而不是import tomli

mport tomli as tomllib

# Python 3.11
#import tomllib

with open("pyproject.toml", "rb") as f:
    config = tomllib.load(f)
    print(config)

toml_string = """
[project]
name = "another-app"
description = "Example Package"
version = "0.1.1"
"""

config = tomllib.loads(toml_string)
print(config)
# {'project': {'name': 'another-app', 'description': 'Example Package', 'version': '0.1.1'}}

Setuptools 最后一个更像是一个弃用通知:

!! 由于 Distutils 已弃用,因此同样不鼓励使用 distutils 中的函数或对象,而 Setuptools 旨在取代或弃用所有此类用途。是时候告别distutils打包并转向setuptools. setuptools文档提供了有关如何替换distutils. 除此之外,PEP 632distutils还为setuptools.

结论

!! 每个新的 Python 版本都会带来新功能,因此我建议你查看Python发行说明中的“新模块”、“弃用模块”和“已删除模块”部分,这是了解 Python 标准重大变化的好方法图书馆。通过这种方式,你可以不断地将新功能和最佳实践融入你的项目中。 现在你可能认为进行所有这些更改和升级需要付出很多努力。实际上,你可以pyupgrade在你的项目上运行并在可能的情况下自动将语法升级到最新的 Python 版本。

猜你喜欢

转载自blog.csdn.net/djstavaV/article/details/131447939
今日推荐