web-dev-qa-db-ja.com

Python周期的なインポートなしのタイプヒント

巨大なクラスを2つに分割しようとしています。基本的に、「メイン」クラスと、次のような追加機能を備えたミックスインです。

main.pyファイル:

import mymixin.py

class Main(object, MyMixin):
    def func1(self, xxx):
        ...

mymixin.pyファイル:

class MyMixin(object):
    def func2(self: Main, xxx):  # <--- note the type hint
        ...

さて、これはうまく機能しますが、もちろんMyMixin.func2の型ヒントは機能しません。 main.pyをインポートすることはできません。周期的なインポートが行われ、ヒントなしではエディター(PyCharm)がselfとは何かを判断できません。

Python 3.4、ソリューションが利用可能な場合は3.5に移行することを望んでいます。

クラスを2つのファイルに分割し、すべての「接続」を保持して、IDEが自動補完とタイプを知っている他のすべての利点を引き続き提供できるようにする方法はありますか?

52
velis

一般的に、インポートサイクルを処理するための非常にエレガントな方法はありません。選択肢は、周期的な依存関係を削除するようにコードを再設計するか、実行できない場合は次のようにします。

# some_file.py

from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from main import Main

class MyObject(object):
    def func2(self, some_param: 'Main'):
        ...

TYPE_CHECKING定数は実行時に常にFalseであるため、インポートは評価されませんが、mypy(および他の型チェックツール)はそのブロックのコンテンツを評価します。

また、Main型の注釈を文字列にする必要があります。実行時にMainシンボルを使用できないため、効果的に前方宣言します。

Python 3.7+を使用している場合、少なくとも PEP 56 を利用して、明示的な文字列注釈を提供する必要をスキップできます。

# some_file.py

from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from main import Main

class MyObject(object):
    # Hooray, cleaner annotations!
    def func2(self, some_param: Main):
        ...

from __future__ import annotations importは、allタイプヒントを文字列にし、評価をスキップします。これは、ここでのコードを少し人間工学に基づいたものにするのに役立ちます。

とはいえ、mypyでmixinを使用するには、現在よりも少し多くの構造が必要になる可能性があります。 Mypy アプローチを推奨 これは基本的にdecezeが記述するものです-MainクラスとMyMixinクラスの両方が継承するABCを作成します。 Pycharmのチェッカーを幸せにするために、似たようなことをする必要が生じたとしても、私は驚かないでしょう。

80
Michael0x2a

大きな問題は、あなたの型が最初から正気でないことです。 MyMixinは、それがMainに混在するというハードコードされた仮定を行いますが、他のクラスに混在させることもできますが、その場合はおそらく壊れます。ミックスインが特定のクラスにミックスされるようにハードコードされている場合、メソッドを分離するのではなく、クラスに直接書き込むこともできます。

適切なタイピングでこれを適切に行うには、MyMixininterface、またはPythonの用語)の抽象クラスに対してコーディングする必要があります。

import abc


class MixinDependencyInterface(abc.ABC):
    @abc.abstractmethod
    def foo(self):
        pass


class MyMixin:
    def func2(self: MixinDependencyInterface, xxx):
        self.foo()  # ← mixin only depends on the interface


class Main(MixinDependencyInterface, MyMixin):
    def foo(self):
        print('bar')
9
deceze

型チェックのみのためにクラスをインポートする場合、循環インポートに苦労している人々のために: Forward Reference (PEP 484-Type Hints)を使用したいでしょう:

型ヒントにまだ定義されていない名前が含まれている場合、その定義は文字列リテラルとして表現され、後で解決される場合があります。

代わりに:

class Tree:
def __init__(self, left: Tree, right: Tree):
    self.left = left
    self.right = right

あなたがやる:

class Tree:
def __init__(self, left: 'Tree', right: 'Tree'):
    self.left = left
    self.right = right
4

私の最初の試みも同様に解決策に非常に近かったことがわかりました。これは私が現在使用しているものです:

# main.py
import mymixin.py

class Main(object, MyMixin):
    def func1(self, xxx):
        ...


# mymixin.py
if False:
    from main import Main

class MyMixin(object):
    def func2(self: 'Main', xxx):  # <--- note the type hint
        ...

インポートされないif Falseステートメント内のインポートに注意してください(ただし、IDEはそれについては知っています)。実行時に不明なので、Mainクラスを文字列として使用します。

2
velis