Python はプログラミング言語ランキングで何度も 1 位を獲得している言語として、誰からも深く愛され、賞賛されています。
Python のリリースごとに、新しいモジュールが追加され、新しいより良い方法が導入されます。私たちは皆、古き良き Python ライブラリや特定の方法を使用することに慣れていますが、今はアップグレードして活用する時期でもあります。新しいモジュールと改良されたモジュールとその機能についても説明します。
パスリブ
pathlib は間違いなく、Python 標準ライブラリに最近追加された最も大きなものの 1 つであり、Python 3.4 から標準ライブラリの一部となっていますが、多くの人は依然としてファイルシステム操作に os モジュールを使用しています。
ただし、pathlib には古い os.path に比べて多くの利点があります。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
パスが文字列ではなくオブジェクトとして扱われるという事実により、オブジェクトを一度作成してから、そのプロパティを検索または操作することも可能になります。
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 の私のお気に入りの機能の 1 つは、/ (「除算」) 演算子を使用してパスを連結できることです。
# Operators:
etc = Path('/etc')
joined = etc / "cron.d" / "anacron"
print(f"Exists? - {joined.exists()}")
# Exists? - True
pathlib は os.path の単なる置き換えであり、os モジュール全体ではないことに注意することが重要です。pathlib には 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
pathlib の新しい関数への os.path 関数の完全なマッピングについては、公式ドキュメントを参照してください。
秘密
os モジュールについて言えば、使用をやめるべきもう 1 つの部分は os.urandom です。代わりに、Python 3.6 以降で利用できる新しい Secret モジュールを使用する必要があります。
# 老方式:
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 モジュールが導入された理由は、random モジュールが暗号化セキュリティ トークンを生成しないにもかかわらず、人々がランダム モジュールを使用してパスワードなどを生成するためです。
ドキュメントによると、random モジュールはセキュリティ目的で使用すべきではなく、secrets または os.urandom を使用する必要がありますが、secrets モジュールの方が新しく、16 進トークンと URL セキュリティ トークン用のユーティリティ/便利なメソッドが含まれているため、間違いなく推奨されます。
ゾーン情報
Python 3.9 より前には、タイムゾーンを操作するための組み込みライブラリがなかったため、誰もが pytz を使用していましたが、現在は標準ライブラリに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 モジュールは、すべてのタイムゾーン操作を抽象基本クラス datetime.tzinfo に委任します。これには、このモジュールをインポートする前に、おそらく pytz から具体的な実装が必要です。標準ライブラリにzoneinfoがあるので、それを使用できるようになりました。
ただし、zoneinfo の使用には注意点があります。これは、システム上でタイム ゾーン データが利用可能であることを前提としています。これは UNIX システムの場合に当てはまります。システムにタイム ゾーン データがない場合は、によって管理されている tzdata パッケージを使用する必要があります。 CPython コア開発者 IANA タイム ゾーン データベースを含むファーストパーティ ライブラリ。
データクラス
Python 3.7 への重要な追加は、namedtuple の代わりとなる dataclasses パッケージです。
なぜnamedtupleを置き換える必要があるのか不思議に思うかもしれません。データ クラスへの切り替えを検討する必要がある理由は次のとおりです。
-
変更可能です
-
デフォルトでは、 repr、eq、init、hash マジック メソッドが提供されます 。
-
デフォルト値を指定できます。
-
継承がサポートされています。
さらに、データ クラスは、 名前付きタプルと同等の機能を提供するために、フローズン属性 と スロット属性 (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
上記のコードには、サイズ比較も含めました。これは、名前付きタプルとデータクラスの大きな違いの 1 つです。上でわかるように、属性を表すためにデータクラスが 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を使用したい場合は、少なくともコレクションのNamedTupleの代わりにtypesモジュールを使用する必要があります。
# 不好方式的:
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
# 更好的方式:
from typing import NamedTuple
class Point(NamedTuple):
x: float
y: float
最後に、名前付きタプルもデータクラスも使用していない場合は、Pydantic を直接使用することを検討してください。
適切なロギング
これは標準ライブラリに最近追加されたものではありませんが、使用する価値はあります。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.
print ステートメントと比較して、上記のシンプルな構成は優れたデバッグ エクスペリエンスを提供します。そして最も重要なことは、ログ ライブラリをさらにカスタマイズして、別の場所にログを記録したり、ログ レベルを変更したり、ログを自動的にローテーションしたりできることです。
F 文字列
Python には、C スタイルの書式設定、f-strings、テンプレート文字列、または .format 関数など、文字列を書式設定するさまざまな方法が含まれていますが、そのうちの 1 つである f-strings は、より自然に記述された書式設定された文字列リテラルです。読みやすく、前述のオプションの中で最も高速です。
したがって、f 文字列が使用される理由について議論したり説明したりする必要はないと思いますが、f 文字列が使用できない特定の状況があります。
% 形式を使用する唯一の理由は、ログを記録するためです。
import logging
things = "something happened..."
logger = logging.getLogger(__name__)
logger.error("Message: %s", things) # 评估内部记录器方法
logger.error(f"Message: {things}") # 立即评估
上の例では、f-strings を使用すると、式はすぐに評価されますが、C スタイルの書式設定では、置換は実際に必要になるまで延期されます。これは、すべてのメッセージが同じテンプレートを持つメッセージのグループ化にとって重要です。は 1 つとしてログに記録できますが、ロガーに渡される前にテンプレートにデータが設定されるため、これは f 文字列では機能しません。
また、実行時のテンプレートの設定など、f-string では単純に実行できないこともあります。つまり、動的書式設定です。これが、f-string がリテラル文字列形式と呼ばれる理由です。
# 动态设置模板及其参数
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-string の方が読みやすく、パフォーマンスが高いため、可能な限り f-string を使用することです。ただし、他の書式設定スタイルが優先されたり必要とされる状況も依然として存在することに注意してください。
トリブ
TOML は広く使用されている構成形式であり、pyproject.toml 構成ファイルで使用されるため、Python のツールとエコシステムにとって特に重要です。これまでは、TOML ファイルを管理するには外部ライブラリを使用する必要がありましたが、Python 3.11 以降では、外部ライブラリを使用する必要がありました。これは、toml パッケージに基づいた、tomllib という組み込みライブラリです。
したがって、Python 3.11 に切り替えたら、import tomli の代わりに import tomllib を使用する習慣を身に付ける必要があります。心配する依存関係が 1 つ減ります。
# import tomli as tomllib
import tomllib
with open("pyproject.toml", "rb") as f:
config = tomllib.load(f)
print(config)
# {'project': {'authors': [{'email': '[email protected]',
# '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'}}
セットアップツール
最後のものは非推奨の通知に似ています。
Distutils は非推奨であるため、distutils の関数やオブジェクトの使用も同様に推奨されず、Setuptools はそのような使用をすべて置き換えるか非推奨にすることを目的としています。
distutils パッケージに別れを告げ、setuptools に切り替える時が来ました。setuptools のドキュメントには、distutils の使用法を置き換える方法に関するガイダンスが記載されています。さらに、PEP 632 では、setuptools でカバーされていない distutils の部分についての移行アドバイスが提供されています。
要約する
Python の新しいバージョンには新しい機能が含まれているため、Python リリース ノートの「新しいモジュール」、「非推奨のモジュール」、「削除されたモジュール」のセクションを確認することをお勧めします。これは、Python 標準を理解する上で重要です。こうすることで、常に新しい機能やベスト プラクティスをプロジェクトに組み込むことができます。