224

巨大なクラスを 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 がオートコンプリートと、タイプを認識してから得られるその他すべての機能を引き続き提供できるようにする方法はありますか?

4

7 に答える 7

333

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

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

于 2016-09-28T20:48:54.037 に答える
18

The bigger issue is that your types aren't sane to begin with. MyMixin makes a hardcoded assumption that it will be mixed into Main, whereas it could be mixed into any number of other classes, in which case it would probably break. If your mixin is hardcoded to be mixed into one specific class, you may as well write the methods directly into that class instead of separating them out.

To properly do this with sane typing, MyMixin should be coded against an interface, or abstract class in Python parlance:

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')
于 2016-09-28T07:52:05.360 に答える
6

Python 3.5 以降、クラスを個別のファイルに分割するのは簡単です。

メソッドをクラスにインポートするために、ブロックimportでステートメントを使用することは実際には可能です。例えば、class ClassName:

class_def.py:

class C:
    from _methods1 import a
    from _methods2 import b

    def x(self):
        return self.a() + " " + self.b()

私の例では、

  • C.a()文字列を返すメソッドになりますhello
  • C.b()を返すメソッドになりますhello goodbye
  • C.x()したがって返されhello hello goodbyeます。

aとを実装するbには、次の手順を実行します。

_methods1.py:

from __future__ import annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from class_def import C

def a(self: C):
    return "hello"

説明:型チェッカーがコードを読み取っているときですTYPE_CHECKINGTrue型チェッカーはコードを実行する必要がないため、if TYPE_CHECKING:ブロック内で発生する循環インポートは問題ありません。__future__インポートにより、延期された注釈が有効になります。これはオプションです。それがなければ、型注釈を引用符で囲む必要があります (つまりdef a(self: "C"):)。

_methods2.py同様に次のように定義します。

from __future__ import annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from class_def import C

def b(self: C):
    return self.a() + " goodbye"

VS Code では、self.a()ホバリング時に 検出された型を確認できます。ここに画像の説明を入力

そして、すべてが期待どおりに実行されます。

>>> from class_def import C
>>> c = C()
>>> c.x()
'hello hello goodbye'

古い Python バージョンに関する注意事項

Python バージョン ≤3.4 の場合、TYPE_CHECKINGは定義されていないため、このソリューションは機能しません。

Python バージョン ≤3.6 では、延期されたアノテーションは定義されていません。from __future__ import annotations回避策として、上記の型宣言を省略して引用符で囲みます。

于 2021-10-03T17:59:22.270 に答える