幾個被淘汰的 Python 庫,請不要再用!
隨著每個 Python 版本的發(fā)布,都會添加新模塊,并引入新的更好的做事方式,雖然我們都習慣了使用好的舊 Python 庫和某些做事方式,但現(xiàn)在也時候升級并利用新的和改進的模塊及其特性了。
Pathlib 而不是 OS
pathlib 絕對是 Python 標準庫中最近添加的更大的內容之一, 自 Python 3.4 以來,它一直是標準庫的一部分,但很多人仍然使用 os 模塊進行文件系統(tǒng)操作。
然而,pathlib 與舊的 os.path 相比具有許多優(yōu)點 - 雖然 os 模塊以原始字符串格式表示路徑,但 pathlib 使用面向對象的樣式,這使得它更具可讀性和編寫自然:
from?pathlib?import?Path
import?os.path
#?老方式
two_dirs_up?=?os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
#?新方式,可讀性強
two_dirs_up?=?Path(__file__).resolve().parent.parent
路徑被視為對象而不是字符串這一事實也使得可以創(chuàng)建一次對象,然后查找其屬性或對其進行操作:
readme?=?Path("README.md").resolve()
print(f"Absolute?path:?{readme.absolute()}")
#?Absolute?path:?/home/martin/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/martin/some/path
print(f"File?extension:?{readme.suffix}")
#?File?extension:?.md
print(f"Is?it?absolute:?{readme.is_absolute()}")
#?Is?it?absolute:?True
我最喜歡 pathlib 的一個特性是可以使用 /(“除法”)運算符來連接路徑:
#?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替代它們。
在上面的片段中,我們展示了一些方便的路徑操作和對象屬性,但 pathlib 還包括你習慣于 os.path 的所有方法,例如:
print(f"Working?directory:?{Path.cwd()}")??#?same?as?os.getcwd()
#?Working?directory:?/home/martin/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/martin/some/path/README.md
print(Path.home())??#?same?as?os.path.expanduser()
#?/home/martin
有關 os.path 函數(shù)到 pathlib 中新函數(shù)的完整映射,請參閱 官方文檔。
Secrets 而不是 OS
說到 os 模塊,你應該停止使用的另一部分是 os.urandom。相反,你應該使用自 Python 3.6 以來可用的新秘密模塊:
#?老方式:
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
#?新方式:
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模塊的原因是因為人們使用隨機模塊來生成密碼等,即使隨機模塊不產生密碼安全令牌。
根據(jù)文檔,隨機模塊不應用于安全目的, 你應該使用 secrets 或 os.urandom,但 secrets 模塊絕對更可取,因為它比較新,并且包含一些用于十六進制令牌的實用程序/便利方法以及 URL 安全令牌。
Zoneinfo 而不是 pytz
在 Python 3.9 之前,沒有用于時區(qū)操作的內置庫,所以每個人都在使用 pytz,但現(xiàn)在我們在標準庫中有 zoneinfo,所以是時候切換了。
from?datetime?import?datetime
import?pytz??#?pip?install?pytz
dt?=?datetime(2022,?6,?4)
nyc?=?pytz.timezone("America/New_York")
localized?=?nyc.localize(dt)
print(f"Datetime:?{localized},?Timezone:?{localized.tzname()},?TZ?Info:?{localized.tzinfo}")
#?新方式:
from?zoneinfo?import?ZoneInfo
nyc?=?ZoneInfo("America/New_York")
localized?=?datetime(2022,?6,?4,?tzinfo=nyc)
print(f"Datetime:?{localized},?Timezone:?{localized.tzname()},?TZ?Info:?{localized.tzinfo}")
#?Datetime:?2022-06-04?00:00:00-04:00,?Timezone:?EDT,?TZ?Info:?America/New_York
datetime 模塊將所有時區(qū)操作委托給抽象基類 datetime.tzinfo, 這個抽象基類需要一個具體的實現(xiàn)——在引入這個很可能來自 pytz 的模塊之前?,F(xiàn)在我們在標準庫中有 zoneinfo,我們可以使用它。
然而,使用 zoneinfo 有一個警告——它假定系統(tǒng)上有可用的時區(qū)數(shù)據(jù),UNIX 系統(tǒng)就是這種情況, 如果你的系統(tǒng)沒有時區(qū)數(shù)據(jù),那么你應該使用 tzdata 包,它是由 CPython 核心開發(fā)人員維護的第一方庫,其中包含 IANA 時區(qū)數(shù)據(jù)庫。
Dataclasses
Python 3.7 的一個重要補充是 dataclasses 包,它是 namedtuple 的替代品。
你可能想知道為什么需要替換 namedtuple?以下是你應該考慮切換到數(shù)據(jù)類的一些原因:
1、它可以是可變的
2、默認提供 repr、eq、init、hash 魔術方法,
3、允許指定默認值,
4、支持繼承。此外,數(shù)據(jù)類還支持 frozen 和 slots(從 3.10 開始)屬性以提供與命名元組的特征奇偶校驗。
切換真的不應該太難,因為你只需要更改定義:
#?老方式:
#?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
#?新方式:
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 和數(shù)據(jù)類之間的較大差異之一,如上所見,命名元組的大小要小得多,這是由于數(shù)據(jù)類使用 dict 來表示屬性。
至于速度比較,除非你計劃創(chuàng)建數(shù)百萬個實例,否則屬性的訪問時間應該基本相同,或者不夠重要:
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
如果以上內容說服了你打算切換到數(shù)據(jù)類,請盡快嘗試吧
相反,如果你不想切換并且出于某種原因真的想使用命名元組,那么你至少應該使用鍵入模塊而不是collections中的 NamedTuple:
#?不好方式的:
from?collections?import?namedtuple
Point?=?namedtuple("Point",?["x",?"y"])
#?更好的方式:
from?typing?import?NamedTuple
class?Point(NamedTuple):
????x:?float
????y:?float
最后,如果你既不使用 namedtuple 也不使用數(shù)據(jù)類,你可能需要考慮直接使用 Pydantic。
Proper Logging 而不是 print
這不是標準庫的最新添加,但值得使用 - 你應該使用正確的日志記錄而不是打印語句, 如果你在本地調試問題,則可以使用 print,但對于任何無需用戶干預即可運行的生產就緒程序,正確的日志記錄是必須的。
特別是考慮到設置 Python 日志記錄非常簡單:
import?logging
logging.basicConfig(
????filename='application.log',
????level=logging.WARNING,
????format='[%(asctime)s]?{%(pathname)s:%(lineno)d}?%(levelname)s?-?%(message)s',
????datefmt='%H:%M:%S'
)
logging.error("Some?serious?error?occurred.")
#?[12:52:35]?{<stdin>:1}?ERROR?-?Some?serious?error?occurred.
logging.warning('Some?warning.')
#?[12:52:35]?{<stdin>:1}?WARNING?-?Some?warning.
與打印語句相比,上面的簡單配置將為你提供卓越的調試體驗, 最重要的是,你可以進一步自定義日志庫以記錄到不同的位置、更改日志級別、自動輪換日志等。
f-strings 而不是 format
Python 包含很多格式化字符串的方法,包括 C 樣式格式化、f 字符串、模板字符串或 .format 函數(shù), 不過,其中之一 - f-strings - 格式化的字符串文字 , 它們寫起來更自然,可讀性更強,并且是前面提到的選項中最快的。
因此,我認為沒有必要爭論或解釋為什么要使用它們,然而,在某些情況下不能使用 f 字符串:
使用 % 格式的唯一原因是用于記錄:
import?logging
things?=?"something?happened..."
logger?=?logging.getLogger(__name__)
logger.error("Message:?%s",?things)??#?評估內部記錄器方法
logger.error(f"Message:?{things}")??#?立即評估
在上面的示例中,如果你使用 f 字符串,則表達式將立即計算,而使用 C 樣式格式,替換將被推遲到實際需要時,這對于消息分組很重要,其中具有相同模板的所有消息都可以記錄為一個, 這不適用于 f 字符串,因為模板在傳遞給記錄器之前填充了數(shù)據(jù)。
此外,有些事情是 f-strings 根本無法做到的, 例如在運行時填充模板 - 即動態(tài)格式 - 這就是 f-strings 被稱為文字字符串格式的原因:
#?動態(tài)設置模板及其參數(shù)
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"))
#?動態(tài)重用具有不同參數(shù)的相同模板.
inputs?=?["Hello",?"World",?"!"]
template?=?"Here's?some?dynamic?value:?{value}"
for?value?in?inputs:
????print(template.format(value=value))
最重要的是,盡可能使用 f 字符串,因為它們更具可讀性和更高性能,但請注意,在某些情況下仍然首選和/或需要其他格式樣式。
Tomllib 而不是 tomli
TOML 是一種廣泛使用的配置格式,對于 Python 的工具和生態(tài)系統(tǒng)尤其重要,因為它用于 pyproject.toml 配置文件, 到目前為止,你必須使用外部庫來管理 TOML 文件,但是從 Python 3.11 開始,將有一個名為 tomllib 的內置庫,它基于 toml 包。
所以,一旦你切換到 Python 3.11,你應該養(yǎng)成使用 import tomllib 而不是 import tomli 的習慣。少了一種需要擔心的依賴!
#?import?tomli?as?tomllib
import?tomllib
with?open("pyproject.toml",?"rb")?as?f:
????config?=?tomllib.load(f)
????print(config)
????#?{'project':?{'authors':?[{'email':?'contact@martinheinz.dev',
????#???????????????????????????'name':?'Martin?Heinz'}],
????#??????????????'dependencies':?['flask',?'requests'],
????#??????????????'description':?'Example?Package',
????#??????????????'name':?'some-app',
????#??????????????'version':?'0.1.0'}}
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 已棄用,因此同樣不鼓勵使用任何來自 distutils 的函數(shù)或對象,Setuptools 旨在替換或棄用所有此類用途。
是時候告別 distutils 包并切換到 setuptools 了,setuptools 文檔提供了有關如何替換 distutils 用法的指導, 除此之外,PEP 632 還為 setuptools 未涵蓋的部分 distutils 提供遷移建議。
總結
每個新的 Python 版本都會帶來新的特性,因此我建議你查看 Python 發(fā)行說明中的“新模塊”、“不推薦使用的模塊”和“已刪除的模塊”部分,這是了解 Python 標準重大變化的好方法 , 通過這種方式,你可以不斷地將新功能和最佳實踐整合到你的項目中。