python / python/mypy

Disallow implicit exports on implicit submodule name bindings in `__init__.py`s

オープン
#11,024 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

bug topic-implicit-reexport
主要言語
Python
スター
20.6k
フォーク
3.3k
PR マージ指標
PR 指標を取得中

説明

Consider the following package and main.py:

.
├── main.py
└── pkg
    ├── __init__.py
    ├── module1.py
    ├── module2.py
    └── utils.py

main.py:

import pkg
pkg.utils.bar1(pkg.foo1())

__init__.py:

__all__ = ('foo1', 'foo2')

from .module1 import foo1
from .module2 import foo2

module1.py:

def foo1() -> int:
    num = 1
    return num

module2.py:

from .utils import bar2

def foo2() -> str:
    num = 2
    return bar2(num)

utils.py:

def bar1(num: int) -> str:
    return 'a'

def bar2(num: int) -> str:
    return 'b'

There are no errors with any of this code; running main.py works fine, type checking with mypy --strict main.py or mypy --strict my_package checks out; but the package arguably contains an inaccuracy. Because of the way the package is written, the consumer script main.py relies on a potentially unstable language detail that is not widely known, although a documented part of the language:

When a submodule is loaded using any mechanism a binding is placed in the parent module’s namespace to the submodule object.

Let’s say that foo2 is deprecated and module2 is not needed any more. If the import line from .module2 import foo2 is removed from __init__.py then nothing would import the pkg.utils module anymore, hence the utils attribute will no longer exist on pkg. This means pkg.utils will no longer work in main.py without an explicit import pkg.utils.

This behaviour is indeed surprising and I wish Python didn’t do this implicit submodule name binding, although I acknowledge this feature has important implications as to how import foo.bar.baz currently behaves. However, I’m sure we can agree that having from .foo import bar leak foo into the namespace inside __init__.py is a common unintended side effect of importing bar.

I think it would be beneficial if Mypy could enforce a new convention to detect and disallow imports of names that exist in __init__.py due to implicit submodule name binding.

For comparison, Pyright does not understand implicit submodule name binding and simply emits a "utils" is not a known member of module in the given scenario.

Expected Behavior
main.py:3: error: Module "pkg" does not explicitly export submodule "utils"; implicit submodule export disabled
Actual Behavior
Success: no issues found in 1 source file
My Environment
  • Mypy version used: 0.910
  • Python version used: 3.9.6
  • Pyright version used: 1.1.163

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

init.py と main.py のパッケージ例から始め、次に mypy --strict main.py で現在の結果を再現します。明示的なエクスポートと暗黙的なサブモジュールバインディングをどのように区別するかを定義します。完了条件は、ドキュメント化された import の動作を維持しながら、例が pkg は utils を明示的にエクスポートしていないと報告することです。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
python
領域
tooling
issue の種類
機能追加
難易度
5/5
見積もり時間
1週間以上
活発さ
停滞
明瞭さ
おおむね明確
初心者へのやさしさ
35/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。