41. パッケージ
#41. パッケージ
パッケージは、他のモジュールを含めることができるモジュールです。 CPython では、パッケージは独立したオブジェクト カテゴリではありません。これはまだモジュール オブジェクトですが、インポート システムにサブモジュールを探す場所を指示するインポート メタデータがあります。
Python レベルでは、このディレクトリをパッケージにすることができます。text app/ __init__.py config.py server.py 次のようにインポートできます。python import app import app.config from app.server import run 重要なルールはシンプルです。```text
A package is a module with submodule search locations.
## 41.1 パッケージはモジュールです
パッケージオブジェクトにはタイプがあります`module`。```python
import email
print(type(email))
print(email.__name__)
```出力:```text
<class 'module'>
email
```パッケージには、他のモジュールと同様にモジュール辞書があります。```python
import email
print(email.__dict__)
```パッケージの名前空間には通常の名前が保存されます。```text
__name__
__doc__
__package__
__loader__
__spec__
__path__
__file__
__cached__
```また、関数、クラス、定数、インポートされたサブモジュール、再エクスポートされたパブリック API 名を保存することもできます。
したがって、パッケージには次の両方が含まれます。```text
a namespace object
a container for submodule lookup
```## 41.2 の役割`__init__.py`通常のパッケージには通常、`__init__.py`ファイル。```text
project/
app/
__init__.py
config.py
routes.py
```CPython がインポートする場合`app`、それは実行します:```text
app/__init__.py
```のコード`__init__.py`パッケージの名前空間を初期化します。
例:```python
# app/__init__.py
VERSION = "1.0.0"
def create_app():
return "app"
```それから:```python
import app
print(app.VERSION)
print(app.create_app())
```ファイル`__init__.py`単なるマーカーではありません。実行可能なモジュールコードです。
## 41.3 最小限のパッケージのインポート
このレイアウトの場合:```text
demo/
__init__.py
util.py
```そしてこのコード:```python
import demo
```CPython は大まかに次のことを実行します。```text
find package named "demo"
create module object for "demo"
set package metadata
insert "demo" into sys.modules
execute demo/__init__.py
bind name "demo" in caller namespace
```インポート後:```python
import sys
import demo
print(sys.modules["demo"] is demo)
```出力:```text
True
```パッケージはキャッシュされています`sys.modules`完全修飾名で。
## 41.4 サブモジュールのインポート
対象:```python
import demo.util
```CPython は最初に親パッケージをインポートします。
概念的には:```text
import demo
then search demo.__path__ for util
then import demo.util
then set demo.util attribute
```インポートが成功した後:```python
import demo.util
print(demo.util)
print(demo.util.__name__)
```出力形状:```text
<module 'demo.util' from '.../demo/util.py'>
demo.util
```サブモジュールは個別にキャッシュされます。```python
import sys
import demo
import demo.util
print(sys.modules["demo"])
print(sys.modules["demo.util"])
print(demo.util is sys.modules["demo.util"])
```親パッケージとサブモジュールは異なるモジュール オブジェクトです。
## 41.5 完全修飾モジュール名
パッケージは階層的なモジュール名を作成します。```text
app
app.config
app.server
app.server.http
```インポートされた各モジュールには完全修飾名が付けられます。```python
import app.server.http
print(app.__name__)
print(app.server.__name__)
print(app.server.http.__name__)
```出力:```text
app
app.server
app.server.http
```完全修飾名は、次で使用されるキーです。`sys.modules`。```python
import sys
print(sys.modules["app"])
print(sys.modules["app.server"])
print(sys.modules["app.server.http"])
```この名前に基づくアイデンティティは重要です。同じファイルを 2 つの異なる名前でインポートすると、2 つの独立したモジュール オブジェクトが作成される可能性があります。
## 41.6 パッケージの検索場所
パッケージには、`__path__`。```python
import app
print(app.__path__)
```通常パッケージの場合、`__path__`通常、パッケージディレクトリが含まれます。```text
['/path/to/project/app']
```CPython がインポートする場合:```python
import app.config
```それは検索します`app.__path__`、完全なトップレベルではありません`sys.path`。
この区別が中心となります。
|インポート |検索パス |
|---|---|
|`import app` | `sys.path` |
| `import app.config` | `app.__path__` |
| `import app.server.http` | `app.server.__path__`|
パッケージは、その子がどこにあるかを制御します。
##41.7`__spec__`とパッケージ
最新のインポートされたモジュールにはそれぞれ、`__spec__`。
パッケージの場合、モジュール仕様にはサブモジュールの検索場所が含まれます。```python
import app
print(app.__spec__)
print(app.__spec__.name)
print(app.__spec__.origin)
print(app.__spec__.submodule_search_locations)
```パッケージの場合、これは通常 null 以外です。```python
app.__spec__.submodule_search_locations
```通常のモジュールの場合、通常は次のようになります。`None`。
インポート システムはこれを使用してモジュールとパッケージを区別します。
## 41.8 パッケージのメタデータ
通常のパッケージには通常、次の属性があります。
|属性 |意味 |
|---|---|
|`__name__`|完全修飾パッケージ名 |
|`__package__`|相対インポートに使用されるパッケージコンテキスト |
|`__path__`|サブモジュールが検索された場所 |
|`__spec__`|輸入仕様 |
|`__loader__`|パッケージを初期化したローダー |
|`__file__`|へのパス`__init__.py`、ファイルバックアップの場合 |
|`__cached__`|キャッシュされたバイトコードへのパス (利用可能な場合) |
例:```python
import json
print(json.__name__)
print(json.__package__)
print(json.__path__)
print(json.__file__)
print(json.__cached__)
```したがって、パッケージは通常のオブジェクトとして観察可能です。
##41.9`__package__`の`__package__`属性は相対的なインポート解像度を制御します。
内部`app/server.py`:
```python
from .config import Settings
```先頭のドットは次のことを意味します。```text
resolve "config" relative to the current package
```モジュール用`app.server`、パッケージのコンテキストは通常次のとおりです。```text
app
```それで:```python
from .config import Settings
```以下のように解決されます。```python
from app.config import Settings
```次のようなパッケージモジュールの場合`app`、`__package__`通常は`"app"`。
次のようなサブモジュールの場合`app.server`、`__package__`通常は`"app"`。
次のようなネストされたサブモジュールの場合`app.http.server`、`__package__`通常は`"app.http"`。
## 41.10 パッケージの実行順序
このレイアウトを考慮すると、次のようになります。```text
app/
__init__.py
config.py
server.py
```そしてこのインポート:```python
import app.server
```CPython は次の順序で実行されます。```text
1. app/__init__.py
2. app/server.py
```もし`server.py`輸入品`config.py`:
```python
# app/server.py
from . import config
```その場合、実行は次のようになります。```text
1. app/__init__.py
2. app/server.py starts
3. app/config.py executes
4. app/server.py continues
```親パッケージは子モジュールの前にロードされます。
## 41.11 サブモジュールのパッケージ属性
後:```python
import app.server
```通常、親パッケージは属性を受け取ります。```python
app.server
```この属性はサブモジュール オブジェクトを指します。
同等のビュー:```python
import sys
import app.server
assert app.server is sys.modules["app.server"]
```ユーザーコードはパッケージ属性を頻繁に移動するため、このバインディングは重要です。```python
import app.server
app.server.run()
```インポート システムは、親パッケージと子モジュール間の接続を維持します。
##41.12`import package.module`対`from package import module`これら 2 つの形式は似ていますが、名前のバインディングが同一ではありません。```python
import app.config
```バインドする`app`呼び出し元の名前空間内。```python
from app import config
```バインドする`config`呼び出し元の名前空間内。
どちらも正常にロードされます`app.config`。
例:```python
import app.config
print(app.config)
from app import config
print(config)
```ロードされるモジュール オブジェクトは通常同じです。```python
import app.config
from app import config
print(app.config is config)
```出力:```text
True
```違いは、インポートするモジュールの名前空間に配置される名前です。
## 41.13 パブリックパッケージAPI
パッケージは、次の方法でクリーンなパブリック API を公開できます。`__init__.py`。
レイアウト例:```text
httpkit/
__init__.py
client.py
response.py
errors.py
```内部ファイル:```python
# httpkit/client.py
class Client:
...
# httpkit/errors.py
class HTTPKitError(Exception):
...
```ファサード:```python
# httpkit/__init__.py
from .client import Client
from .errors import HTTPKitError
__all__ = ["Client", "HTTPKitError"]
```ユーザーは以下を書くことができます:```python
from httpkit import Client, HTTPKitError
```これにより、パッケージ作成者はパブリック インポートを維持しながら内部ファイル レイアウトを変更できます。
##41.14`__all__`名前`__all__`スターインポートで使用されるパブリック名を定義します。```python
__all__ = ["Client", "HTTPKitError"]
```のために:```python
from httpkit import *
```Python は、以下にリストされている名前をインポートします。`httpkit.__all__`。
それなし`__all__`、スターインポートは、で始まらない名前をエクスポートします。`_`。`__all__`ドキュメントとしても役立ちます。これにより、どの名前がパブリック パッケージ API として意図されているかが読者にわかります。
## 41.15 パッケージのファサードと輸入コスト
パッケージのファサードにより人間工学は改善されますが、インポート時間が長くなる可能性があります。
これは便利です:```python
# package/__init__.py
from .database import Database
from .server import Server
from .client import Client
from .analytics import Tracker
```しかし今は:```python
import package
```これらすべてのモジュールをロードします。
これらのモジュールが大規模な依存関係をインポートしたり、ネイティブ ライブラリを初期化したり、ファイルを読み取ったり、構成を実行したりする場合、コストがかかる可能性があります。
より軽量なパッケージのファサードでは、安価な名前のみが公開される可能性があります。```python
# package/__init__.py
__version__ = "1.0.0"
```次に、ユーザーは重いコンポーネントを直接インポートします。```python
from package.client import Client
```優れたパッケージ設計では、利便性、起動時間、依存関係の明確さのバランスが取れています。
## 41.16 遅延パッケージの属性
パッケージはモジュールレベルを使用して属性を遅延公開できます`__getattr__`。```python
# package/__init__.py
def __getattr__(name):
if name == "Client":
from .client import Client
return Client
raise AttributeError(name)
```それから:```python
import package
Client = package.Client
```輸入品`.client`そのときだけ`Client`と要求される。
これにより、優れたパブリック API を維持しながら、起動コストを削減できます。
トレードオフは複雑さです。パッケージのエクスポートが遅延すると、インポートの動作がわかりにくくなり、後でエラーが表示される可能性があります。
## 41.17 通常パッケージ
通常のパッケージには、`__init__.py`。```text
pkg/
__init__.py
mod.py
```プロパティ:```text
executes __init__.py when imported
has __file__ pointing to __init__.py
has __path__ pointing to package directory
can define package-level API
can contain submodules and subpackages
```ほとんどのアプリケーション パッケージとライブラリは通常のパッケージを使用します。
## 41.18 名前空間パッケージ
名前空間パッケージには単一の名前空間はありません`__init__.py`。
複数のディレクトリから組み立てることができます。
例:```text
dir1/
plugins/
alpha.py
dir2/
plugins/
beta.py
```両方の場合`dir1`そして`dir2`オンです`sys.path`、 それから`plugins`名前空間パッケージにすることもできます。```python
import plugins.alpha
import plugins.beta
```パッケージ`plugins`あるかもしれない`__path__`両方の場所が含まれています。
名前空間パッケージは、複数のディストリビューションが 1 つのパッケージ名前空間に貢献している場合に役立ちます。
## 41.19 通常パッケージと名前空間パッケージ
|特集 |通常パッケージ |名前空間パッケージ |
|---|---|---|
|もっている`__init__.py`|はい |いいえ |
|パッケージ初期化コードを実行します |はい |単一の初期化子はありません |
|パッケージレベルの名前を直接定義できる |はい |限定 |
|複数のディレクトリにまたがることも可能 |通常はいいえ |はい |
|共通用途 |通常のライブラリとアプリ |プラグインの名前空間、分割ディストリビューション |
通常のパッケージでは明示的な初期化が行われます。名前空間パッケージにより、柔軟な構成が可能になります。
## 41.20 サブパッケージ
サブパッケージは、別のパッケージ内のパッケージです。```text
app/
__init__.py
api/
__init__.py
users.py
posts.py
```以下をインポートできます。```python
import app.api
import app.api.users
```インポート システムは各レベルを解決します。```text
app
app.api
app.api.users
```各レベルには独自のモジュール オブジェクトと独自の`sys.modules`エントリ。```python
import sys
import app.api.users
print(sys.modules["app"])
print(sys.modules["app.api"])
print(sys.modules["app.api.users"])
```## 41.21 パッケージ内の相対インポート
相対インポートはパッケージ内で一般的です。```python
from .config import Settings
from .storage import Store
from ..core import errors
```Dots mean levels:
|構文 |意味 |
|---|---|
|`from . import x`|現在のパッケージから兄弟をインポート |
|`from .x import y`|現在のパッケージの子モジュールからインポート |
|`from .. import x`|親パッケージからインポート |
|`from ..x import y`|親パッケージの下の兄弟からインポート |
相対インポートでは、内部依存関係がトップレベルのパッケージ名から独立します。
また、正しいパッケージ コンテキストも必要です。パッケージ ファイルを直接実行すると、パッケージが壊れる可能性があります。
## 41.22 スクリプトの直接実行の問題
与えられる:```text
app/
__init__.py
main.py
config.py
```内部`main.py`:
```python
from .config import Settings
```これは機能します:```bash
python -m app.main
```これは失敗する可能性があります:```bash
python app/main.py
```ファイルパスで実行すると、`main.py`になる`__main__`、 ない`app.main`。モジュールは、相対インポートに必要なパッケージ コンテキストを失う可能性があります。
パッケージ コードにモジュール実行を使用します。```bash
python -m app.main
```##41.23`__main__.py`パッケージで定義できるのは、`__main__.py`。```text
tool/
__init__.py
__main__.py
cli.py
```それから:```bash
python -m tool
```実行します:```text
tool/__main__.py
```例:```python
# tool/__main__.py
from .cli import main
main()
```これは、パッケージを実行可能にする標準的な方法です。
## 41.24 パッケージ初期化の副作用
なぜなら`__init__.py`インポート中に実行されるため、パッケージの初期化には副作用が生じる可能性があります。```python
# package/__init__.py
print("loading package")
connect_to_service()
```それから:```python
import package
```その作業をすぐに実行します。
これは次の場合に問題となる可能性があります。```text
startup time
tests
CLI responsiveness
server cold starts
configuration ordering
optional dependencies
import cycles
```可能な限りパッケージの初期化を小さくしてください。
良い`__init__.py`通常、ファイルには次のものが含まれます。```text
version constants
cheap re-exports
small compatibility shims
public API declarations
```インポート時の初期化が明示的なパッケージ契約の一部である場合を除き、負荷のかかる作業は避けてください。
## 41.25 パッケージの依存関係グラフ
パッケージ構造は依存関係の方向を反映する必要があります。
クリーンなアプリケーションは次のようになります。```text
app/
__init__.py
main.py
config.py
domain/
__init__.py
users.py
posts.py
storage/
__init__.py
db.py
web/
__init__.py
routes.py
```依存関係の良い方向性:```text
main imports web
web imports domain
web imports storage
storage imports domain
domain imports no app-specific infrastructure
```依存関係の方向性が不適切:```text
domain imports web
storage imports main
config imports route handlers
__init__.py imports everything
```不適切なパッケージ依存関係グラフでは、循環インポートが生成されることがよくあります。
## 41.26 パッケージ内の循環インポート
モジュールは兄弟をインポートすることが多いため、パッケージでは循環インポートが一般的です。
例:```python
# app/users.py
from .posts import Post
class User:
...
# app/posts.py
from .users import User
class Post:
...
```これは失敗する可能性があります。`app.users`そして`app.posts`トップレベルの実行中にお互いを必要とします。
考えられる修正:
共有定義を移動します。```text
app/
models.py
users.py
posts.py
```ローカルインポートを使用します。```python
def create_post():
from .posts import Post
return Post()
```型チェックのみのインポートを使用します。```python
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .posts import Post
```通常は構造的な修正が最善です。循環インポートは、多くの場合、モジュール境界の調整が必要であることを示します。
## 41.27 パッケージレベルの再エクスポートと循環インポート
再輸出が多すぎる`__init__.py`輸入サイクルを生み出すことができます。
例:```python
# app/__init__.py
from .server import Server
from .config import Config
```それから中へ`server.py`:
```python
from app import Config
```これにより、`app.__init__`インポート中に終了するには`server`。
より安全な内部インポートは次のとおりです。```python
from .config import Config
```パッケージ内では、パッケージ ファサードからではなく定義モジュールからインポートすることを優先します。
パッケージのファサードは主に外部ユーザー向けです。
## 41.28 プライベートモジュール
Python はプライベート モジュールに命名規則を使用します。```text
package/
__init__.py
public.py
_internal.py
_compat.py
```先頭のアンダースコアは、慣例によりモジュールが内部であることを意味します。```python
from package._internal import helper
```これは許可されていますが、パッケージの作成者は変更される可能性があります`_internal`互換性を維持せずに。
パブリック API は文書化し、意図的に再エクスポートする必要があります。
##41.29`src`レイアウト
多くの Python プロジェクトでは、`src`レイアウト。```text
project/
pyproject.toml
src/
package/
__init__.py
core.py
tests/
test_core.py
```このレイアウトは、リポジトリ ルートからの誤ったインポートを防ぐのに役立ちます。
それなし`src`、インストールされたパッケージが壊れている場合でも、テストは誤ってローカル ファイルをインポートする可能性があります。
と`src`、パッケージがインストールされているか、パスが正しく構成されている必要があります。これは、ユーザーの動作によりよく一致します。
## 41.30 パッケージデータ
パッケージにはデータ ファイルを含めることができます。```text
package/
__init__.py
templates/
page.html
data/
defaults.json
```パッケージ データが通常のファイル システム ディレクトリに存在すると想定しないでください。パッケージは、zip ファイルまたは他のローダーからインポートできます。
好む`importlib.resources`:
```python
from importlib.resources import files
data = files("package.data").joinpath("defaults.json").read_text()
```これにより、手動でパスを構築するのではなく、インポート システムにリソースが要求されます。`__file__`。
##41.31`__file__`制限事項
多くのパッケージには、`__file__`。```python
import package
print(package.__file__)
```ただし、堅牢なコードでは、すべてのモジュールとパッケージに通常のファイル パスがあると想定すべきではありません。
一部のモジュールは次のとおりです。```text
built in
frozen
loaded from zip files
loaded by custom importers
namespace packages
```パッケージ リソースの場合は、可能な場合は直接パス演算ではなくインポート システム API を使用してください。
## 41.32 パッケージのバージョン値
パッケージでは多くの場合、`__version__`。```python
# package/__init__.py
__version__ = "1.2.3"
```これは単純かつ一般的なことです。
パッケージは、インストールされたパッケージのメタデータを使用することもあります。```python
from importlib.metadata import version
__version__ = version("package-name")
```2 番目のアプローチでは、バージョン文字列の重複を回避できますが、パッケージがメタデータとしてインストールされていない場合は失敗する可能性があります。
ライブラリの場合は、バージョンの処理をシンプルかつ予測可能なものにしてください。
## 41.33 パブリック API の安定性
パッケージレイアウトとパブリックAPIは別のものです。
内部レイアウト:```text
library/
_client.py
_transport.py
_errors.py
```パブリック API:```python
from library import Client, LibraryError
```パッケージは内部を変更しながらパブリック API を維持できます。```python
# library/__init__.py
from ._client import Client
from ._errors import LibraryError
__all__ = ["Client", "LibraryError"]
```この分離により、保守者は自由にリファクタリングできるようになります。
## 41.34 パッケージのインポート時間
パッケージのインポート時間を計測できます。```bash
python -X importtime -c "import package"
```パッケージのインポートが遅い原因は次のことがよくあります。```text
large transitive imports
heavy package __init__.py files
runtime configuration loading
native library initialization
network or file-system work
plugin auto-discovery
large type-hint imports at runtime
```改善は通常、作成することから始まります`__init__.py`より小さい。
## 41.35 パッケージの初期化パターン
実用的な`__init__.py`多くの場合、次のようになります。```python
"""
Public API for examplekit.
"""
from .client import Client
from .errors import ExampleKitError
__all__ = [
"Client",
"ExampleKitError",
]
__version__ = "0.1.0"
```これは次の場合に許容されます`client`そして`errors`輸入すると安いです。
より重いモジュールの場合:```python
__all__ = ["Client", "ExampleKitError", "__version__"]
__version__ = "0.1.0"
def __getattr__(name):
if name == "Client":
from .client import Client
return Client
if name == "ExampleKitError":
from .errors import ExampleKitError
return ExampleKitError
raise AttributeError(name)
```起動コストが複雑さを正当化する場合にのみ、遅延形式を使用してください。
## 41.36 パッケージと CPython の内部構造
CPython レベルでは、パッケージのインポートは依然としてモジュールのインポートです。
違いはインポート メタデータに現れます。```text
regular module:
__spec__.submodule_search_locations = None
package:
__spec__.submodule_search_locations = [...]
__path__ = [...]
```子モジュールをインポートする場合、インポート システムは親パッケージのパスを使用します。
簡略化:```python
def import_child(parent, child_name):
fullname = parent.__name__ + "." + child_name
path = parent.__path__
spec = find_spec(fullname, path)
return load(spec)
```実際のインポート システムは、ロック、エラー、名前空間パッケージ、キャッシュ、ローダー プロトコルを処理します。
## 41.37 パッケージオブジェクトと属性の検索
パッケージ オブジェクトは、通常のモジュール属性検索を使用します。```python
import package
package.name
```これはパッケージ辞書で調べられます。
パッケージがモジュールレベルを定義している場合`__getattr__`、欠落している属性は動的に計算できます。```python
def __getattr__(name):
...
```ただし、インポートされたサブモジュールは通常、親パッケージの属性として保存されます。```python
import package.submodule
print(package.submodule)
```これが、インポートが発生するにつれてパッケージの名前空間が拡大する可能性がある理由です。
## 41.38 失敗したパッケージのインポート
パッケージのインポートが途中で失敗した場合`__init__.py`、インポート システムは失敗したモジュールを`sys.modules`多くの場合。
例:```python
# broken/__init__.py
raise RuntimeError("failed")
```それから:```python
import broken
```例外が発生します。
インポート システムは、壊れたモジュールが正常に初期化されたかのようにキャッシュされたままになることを避ける必要があります。
サブモジュールの障害は、より微妙な場合があります。子モジュールが失敗しても、親パッケージは正常にインポートされる場合があります。
## 41.39 パッケージ ID の重複
パッケージ ID の重複は、同じパッケージを別の名前でインポートできる場合に発生します。
パスの問題の例:```text
project/
app/
__init__.py
state.py
```1 つのインポート パス:```python
import app.state
```もう一つの偶然のパス:```python
import state
```同じファイルを 2 回ロードできるようになりました。```text
sys.modules["app.state"]
sys.modules["state"]
```結果:```text
two module global dictionaries
two singleton instances
two registry objects
two class identities
two caches
```これは奇妙なバグの一般的な原因です。
一貫した絶対パッケージのインポートを使用し、安全でないものを回避することでこれを回避します。`sys.path`編集します。
## 41.40 パッケージと型の識別
クラスはモジュールに格納されるオブジェクトです。
同じモジュールが異なる名前で 2 回インポートされると、そのクラスは 2 回作成されます。```python
# app/models.py
class User:
pass
```両方としてロードされた場合:```python
import app.models
import models
```それから:```python
app.models.User is models.User
```多分:```text
False
```1 つのクラスから作成されたオブジェクトが失敗する可能性がある`isinstance`他のものに対してチェックします。
これが、モジュールのアイデンティティがパッケージにとって重要である理由です。
## 41.41 パッケージとエントリーポイント
インストールされたパッケージは、パッケージ化メタデータを通じてコンソール スクリプトを公開できます。
コマンドライン エントリ ポイントは以下を呼び出すことができます。```text
package.module:function
```概念的には、コマンドを実行するとモジュールがインポートされ、関数が呼び出されます。
ターゲットの例:```python
# tool/cli.py
def main():
...
```エントリーポイント:```text
tool = tool.cli:main
```つまり、CLI の起動コストにはインポートが含まれます。`tool.cli`とその依存関係。
起動が重要な場合は、CLI エントリ モジュールを小さくしてください。
## 41.42 プラグインパッケージ
多くの場合、パッケージはプラグインをサポートしています。
プラグイン アーキテクチャでは次のものを使用できます。```text
namespace packages
entry points
importlib
explicit plugin lists
dynamic discovery
```シンプルな明示的なプラグイン ローダー:```python
import importlib
def load_plugin(name):
return importlib.import_module(f"app_plugins.{name}")
```パッケージベースのプラグイン システムでは、必要な場合を除き、すべてのプラグインを熱心にインポートすることは避けるべきです。
プラグインのインポートには、登録などの副作用が伴うことがよくあります。```python
# plugin_alpha.py
from registry import register
register("alpha", handler)
```これは便利ですが、インポート順序はプログラムの動作の一部になります。
## 41.43 パッケージとディストリビューション名
インポート パッケージ名と配布パッケージ名は異なる場合があります。
例:```text
distribution name: beautifulsoup4
import name: bs4
```インポート システムはインポート名を認識します。パッケージ化ツールはディストリビューション名を認識します。
この区別は、依存関係リストまたはパッケージのメタデータを読み取るときに重要になります。```python
import bs4
```インストールされているディストリビューションの名前が指定されていないとは言えません`bs4`。
## 41.44 パッケージと`pyproject.toml`最新の Python パッケージで一般的に使用されるのは、`pyproject.toml`。
プロジェクトでは次のように宣言できます。```toml
[project]
name = "examplekit"
version = "0.1.0"
```ディストリビューション名は、`examplekit`。
インポート パッケージは次のとおりです。```text
src/examplekit/
__init__.py
```CPython のインポート システムの場合、インポート可能なパッケージ レイアウトと`sys.path`実行時に問題になります。ビルド メタデータは、実行前、インストールおよびパッケージ化中に重要です。
## 41.45 編集可能なインストール
開発では、パッケージは編集可能モードでインストールされることがよくあります。```bash
python -m pip install -e .
```これにより、パッケージが作業ツリーからインポート可能になります。
CPython の観点から見ると、結果は依然としてパスベースのインポートです。パッケージのソースの場所がインポート解像度に表示されるように環境が構成されています。
編集可能なインストールは、ライブ ソース ファイルを使用しながら、ユーザーと同じようにテストとツールがパッケージをインポートするのに役立ちます。
## 41.46 パッケージのデバッグチェックリスト
パッケージのインポートが異常な動作をする場合は、以下を検査してください。```python
import sys
import package
print(package)
print(package.__name__)
print(getattr(package, "__file__", None))
print(getattr(package, "__path__", None))
print(package.__spec__)
print(sys.modules.get("package"))
```サブモジュールの場合:```python
import package.submodule
print(package.submodule)
print(package.submodule.__name__)
print(package.submodule.__file__)
print(sys.modules.get("package.submodule"))
```パスの問題の場合:```python
import sys
for p in sys.path:
print(p)
```インポートせずに解決するには:```python
import importlib.util
print(importlib.util.find_spec("package"))
print(importlib.util.find_spec("package.submodule"))
```## 41.47 最小パッケージインポートモデル
サブモジュールをインポートするための簡略化されたモデル:```python
def import_package_child(parent_name, child_name):
parent = import_module(parent_name)
fullname = parent_name + "." + child_name
if fullname in sys.modules:
return sys.modules[fullname]
spec = find_spec(fullname, parent.__path__)
if spec is None:
raise ModuleNotFoundError(fullname)
module = module_from_spec(spec)
sys.modules[fullname] = module
try:
spec.loader.exec_module(module)
except Exception:
del sys.modules[fullname]
raise
setattr(parent, child_name, module)
return module
```このモデルは、パッケージ固有の部分をキャプチャします。```text
load parent first
search parent.__path__
cache child by fully qualified name
bind child as parent attribute
```## 41.48 優れたパッケージデザインのルール
パッケージの初期化は小さくしてください。
使用`__init__.py`アプリケーションを実行するためではなく、安定したパブリック API を公開するためです。
定義モジュールからの直接内部インポートを優先します。
内部モジュールをパッケージ ファサードからインポートすることは避けてください。
パッケージ モジュールに対するスクリプトの直接実行は避けてください。使用`python -m package.module`。
使用`importlib.resources`パッケージデータ用。
使用`__all__`公開名を文書化するため。
名前空間パッケージは、分割パッケージ構成が必要な場合にのみ使用してください。
変更を避ける`sys.path`パッケージ内のコード。
依存関係の方向を明確にしておきます。
## 41.49 一般的なパッケージの障害
|症状 |考えられる原因 |
|---|---|
|相対インポートに失敗しました |モジュールではなくスクリプトとして実行されるモジュール`-m`|
|部分的に初期化されたパッケージ |循環インポート |
|パッケージ属性がありません |サブモジュールがインポートされていないか、ファサードがサブモジュールをエクスポートしていません。
| 2 つのシングルトン インスタンス |同じモジュールが 2 つの名前でインポートされました。
|遅い`import package`|重い`__init__.py`または推移的なインポート |
|リポジトリでは動作しますが、インストール後に失敗します。パッケージのレイアウトが間違っている、またはパッケージ データが欠落している |
|標準モジュールのシャドウ |ローカル ファイルまたはパッケージ名の衝突 |
## 41.50 重要なポイント
パッケージは、サブモジュールの検索場所を持つモジュールです。
通常のパッケージが実行される`__init__.py`輸入中。
サブモジュールはキャッシュされます`sys.modules`完全修飾名で。
パッケージの`__path__`子モジュールが検索される場所を制御します。
相対インポートはパッケージのコンテキストに依存します。
名前空間パッケージでは、1 つのパッケージ名前空間が複数の場所にまたがることができます。
パッケージ ファサードは API の人間工学を改善しますが、インポート コストが増加し、サイクルが発生する可能性があります。
パッケージのバグのほとんどは、循環インポート、パスの間違い、モジュール ID の重複、大量の初期化、またはパッケージ ファイルの直接実行によって発生します。