Подтвердить что ты не робот

Подсказка типа Python без циклического импорта

Я пытаюсь разделить свой огромный класс на две части; ну, в основном в "основной" класс и миксин с дополнительными функциями, вот так:

файл 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, если решение там доступно.

Есть ли способ, которым я могу разделить свой класс на два файла и сохранить все "соединения", чтобы моя IDE все еще предлагала мне автозаполнение и все другие полезности, которые приходят из него, зная типы?

4b9b3361

Ответ 1

Боюсь, что в целом не существует элегантного способа обработки циклов импорта. Вы можете либо изменить код, чтобы удалить циклическую зависимость, либо, если это невозможно, сделать что-то вроде этого:

# 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 импорта from __future__ import annotations сделает все подсказки типов строками и пропустит их оценку. Это может сделать наш код немного более эргономичным.

Все это говорит о том, что использование mixins с mypy, вероятно, потребует немного большей структуры, чем вы в настоящее время имеете. Mypy рекомендует подход, который в основном deceze то, что deceze - создать азбуку, которую наследуют MyMixin классы Main и MyMixin. Я не удивлюсь, если вам понадобится сделать что-то подобное, чтобы осчастливить Пичарма.

Ответ 2

Большая проблема заключается в том, что ваши типы не являются нормальными для начала. MyMixin делает твердое предположение о том, что оно будет смешано с Main, тогда как оно может быть смешано с любым числом других классов, и в этом случае оно, вероятно, сломается. Если ваш микшинг жестко запрограммирован для смешивания в один конкретный класс, вы можете также написать методы непосредственно в этот класс, а не разделять их.

Чтобы правильно сделать это с помощью правильной типизации, 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')

Ответ 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, который никогда не импортируется (но IDE знает об этом в любом случае) и использует класс Main как строку, поскольку он не известен во время выполнения.

Ответ 4

Вы всегда можете импортировать пакеты вместо имен:

# main.py
import mymixin

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

 

# mymixin.py
import main

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

Это поможет вам избежать проблем с циклическими зависимостями.

Ответ 5

Я думаю, что идеальным способом было бы импортировать все классы и зависимости в файле (например, __init__.py), а затем from __init__ import * во всех других файлах.

В этом случае вы

  • избегать множественных ссылок на эти файлы и классы и
  • также нужно добавить только одну строку в каждый из других файлов и
  • третий будет пищажем, знающим обо всех классах, которые вы можете использовать.

Ответ 6

Для людей, которые борются с циклическим импортом при импорте класса только для проверки типов: вы, вероятно, захотите использовать прямую ссылку (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