循環インポートなしのPython型ヒント


108

私は私の巨大なクラスを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が自動補完と、タイプを知っていることから来る他のすべての便利な機能を提供できる方法はありますか?


2
self常に現在のクラスのサブクラスになるため、通常はの型に注釈を付ける必要はないと思います(そして、任意の型チェックシステムはそれ自体でそれを理解できるはずです)。で定義されていないfunc2を呼び出そうとしていますか?多分それは(として、多分)すべきですか?func1MyMixinabstractmethod
Blckknght 2016

また、ノート、一般的に、より固有のクラス(例えば、あなたのミックスイン)は、すなわちクラス定義に残っベースのクラスに行く必要があることをclass Main(MyMixin, SomeBaseClass)より多くの固有のクラスからメソッドが基本クラスからのものを上書きすることができますように
Anentropic

3
これらのコメントは、尋ねられる質問に接しているため、どのように役立つかわかりません。velisはコードレビューを求めていませんでした。
Jacob Lee

インポートされたクラスメソッドを使用したPythonの型ヒントは、問題に対するエレガントなソリューションを提供します。
ベンマレス

回答:


164

一般に、インポートサイクルを処理するための非常にエレガントな方法はありません。コードを再設計して循環依存関係を削除するか、それが可能でない場合は、次のようにします。

# 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 563

# 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インポートは行います、すべてのタイプのヒントは文字列で、それらを評価するスキップ。これにより、コードを少し人間工学的にすることができます。

そうは言っても、mypyでミックスインを使用するには、おそらく現在よりも少し多くの構造が必要になります。Mypy は、基本的にdecezeは記述されているアプローチお勧めします- MainMyMixinクラスの両方が継承するABCを作成します。Pycharmのチェッカーを幸せにするために同様のことをする必要があったとしても、私は驚かないでしょう。


3
これをありがとう。私の現在のpython 3.4にははありませんがtyping、PyCharmもかなり満足しif False:ています。
velis 2016

唯一の問題は、MyObjectがDjangoモデルとして認識されないことです。したがって、インスタンス属性が外部で定義されていることについて悩みます__init__
velis

ここに対応するPEPはあるtyping. TYPE_CHECKING python.org/dev/peps/pep-0484/#runtime-or-type-checking
Conchylicultor

24

タイプチェックのためだけにクラスをインポートするときに循環インポートに苦労している人のために:前方参照を使用することになるでしょう(PEP 484-タイプヒント):

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

だから代わりに:

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

PyCharmかもしれません。最新バージョンを使用していますか?試しましたFile -> Invalidate Cachesか?
Tomasz Bartkowiak

ありがとう。コメントを削除しました。これはうまくいくと述べていましたが、PyCharmは不平を言っています。Velisによって提案されたif Falseハックを使用して解決しました。キャッシュを無効にしても解決しませんでした。おそらくPyCharmの問題です。
Jacob Lee

1
@JacobLeeの代わりにif False:次のことが可能にもfrom typing import TYPE_CHECKINGしてif TYPE_CHECKING:
ラッキードナルド

11

より大きな問題は、あなたの型はそもそも正気ではないということです。MyMixinに混合されるというハードコードされた仮定をしますMain他の任意の数のクラスに混合される可能性があり、その場合はおそらく壊れます。ミックスインが1つの特定のクラスに混合されるようにハードコーディングされている場合は、メソッドを分離するのではなく、そのクラスに直接メソッドを書き込むこともできます。

正しいタイピングでこれを適切に行うにはMyMixinインターフェース、または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')

1
まあ、私は私のソリューションが素晴らしいと言っているのではありません。これは、コードを管理しやすくするために私がやろうとしていることです。あなたの提案は成功するかもしれませんが、これは実際には、私の特定のケースでは、Mainクラス全体をインターフェイスに移動することを意味します。
velis 2016

3

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

# 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ことのないステートメント内のimport (ただし、IDEはそれについて知っている)とMain、実行時にクラスが不明であるため、クラスを文字列として使用することに注意してください。


これにより、デッドコードに関する警告が表示されると思います。
Phil

@Phil:はい、当時はPython 3.4を使用していました。今すぐそこのtyping.TYPE_CHECKING
velis

-4

完璧な方法は、すべてのクラスと依存関係をファイル(のように__init__.py)にインポートしてからfrom __init__ import *、他のすべてのファイルにインポートすることです。

この場合、あなたは

  1. これらのファイルとクラスへの複数の参照を回避し、
  2. また、他の各ファイルに1行追加するだけで、
  3. 3番目は、使用する可能性のあるすべてのクラスを知っているpycharmです。

1
これは、あらゆる場所ですべてをロードしていることを意味します。かなり重いライブラリがある場合は、すべてのインポートでライブラリ全体をロードする必要があることを意味します。+リファレンスは非常に遅くなります。
Omer Shacham

>それはあなたがあらゆる場所にあらゆるものをロードしていることを意味します。>>>>絶対にあなたは「多くの持っていた場合のinitの.py」やその他のファイル、および回避をimport *、まだあなたはこの簡単なアプローチを活用することができます
SławomirLenart
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.