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

Python круговой импорт еще раз (ака неправильный с этим дизайном)

Рассмотрим скрипты python (3.x):

main.py:

from test.team import team
from test.user import user

if __name__ == '__main__':
    u = user()
    t = team()
    u.setTeam(t)
    t.setLeader(u)

Тест/user.py:

from test.team import team

class user:
    def setTeam(self, t):
        if issubclass(t, team.__class__):
            self.team = t

Тест/team.py:

from test.user import user

class team:
    def setLeader(self, u):
        if issubclass(u, user.__class__):
            self.leader = u

Теперь, конечно, у меня есть круговой импорт и великолепный ImportError.

Итак, не будучи pythonista, у меня есть три вопроса. Прежде всего:

я. Как я могу заставить эту работу работать?

И, зная, что кто-то неизбежно скажет: "Циркулярный импорт всегда указывает на проблему дизайна", возникает второй вопрос:

II. Почему этот дизайн плохой?

И наконец, третий:

III. Что было бы лучше альтернативой?

Чтобы быть точным, проверка типа, как указано выше, является лишь примером, существует также индексный слой, основанный на классе, который позволяет, например,. найти всех пользователей, являющихся членами одной команды (класс пользователя имеет много подклассов, поэтому индекс удваивается, для пользователей в целом и для каждого конкретного подкласса) или все команды, предоставившие пользователю в качестве участника

Edit:

Надеюсь, что более подробный пример прояснит, чего я пытаюсь достичь. Файлы, опущенные для чтения (но наличие одного исходного файла в 300 КБ меня как-то пугает, поэтому, пожалуйста, предположите, что каждый класс находится в другом файле)

# ENTITY

class Entity:
    _id    = None
    _defs  = {}
    _data  = None

    def __init__(self, **kwargs):
        self._id   = uuid.uuid4() # for example. or randint(). or x+1.
        self._data = {}.update(kwargs)

    def __settattr__(self, name, value):
        if name in self._defs:
            if issubclass(value.__class__, self._defs[name]):
                self._data[name] = value

                # more stuff goes here, specially indexing dependencies, so we can 
                # do Index(some_class, name_of_property, some.object) to find all   
                # objects of some_class or its children where
                # given property == some.object

            else:
                raise Exception('Some misleading message')
        else:
            self.__dict__[name] = value    

    def __gettattr__(self, name):
        return self._data[name]

# USERS 

class User(Entity):
    _defs  = {'team':Team}

class DPLUser(User):
    _defs  = {'team':DPLTeam}

class PythonUser(DPLUser)
    pass

class PerlUser(DPLUser)
    pass

class FunctionalUser(User):
    _defs  = {'team':FunctionalTeam}

class HaskellUser(FunctionalUser)
    pass

class ErlangUser(FunctionalUser)
    pass

# TEAMS

class Team(Entity):
    _defs  = {'leader':User}

class DPLTeam(Team):
    _defs  = {'leader':DPLUser}

class FunctionalTeam(Team):
    _defs  = {'leader':FunctionalUser}

и теперь некоторое использование:

t1 = FunctionalTeam()
t2 = DLPTeam()
t3 = Team()

u1 = HaskellUser()
u2 = PythonUser()

t1.leader = u1 # ok
t2.leader = u2 # ok
t1.leader = u2 # not ok, exception
t3.leader = u2 # ok

# now , index

print(Index(FunctionalTeam, 'leader', u2)) # -> [t2]
print(Index(Team, 'leader', u2)) # -> [t2,t3]

Итак, он отлично работает (детали реализации опущены, но нет ничего сложного) помимо этой нечестивой круговой импортной вещи.

4b9b3361

Ответ 1

Циркулярный импорт по своей сути не является плохим. Естественно для кода team полагаться на user, а user делает что-то с team.

Хуже практика здесь from module import member. Модуль team пытается получить класс user во время импорта, а модуль user пытается получить класс team. Но класс team еще не существует, потому что вы все еще находитесь в первой строке team.py, когда выполняется user.py.

Вместо этого импортируйте только модули. Это приводит к более четкому пространству имен, делает возможной последующую патчу обезьяны и решает проблему импорта. Поскольку вы импортируете модуль только во время импорта, вам все равно, чем класс внутри него еще не определен. К тому времени, когда вы начнете использовать класс, это будет.

Итак, test/users.py:

import test.teams

class User:
    def setTeam(self, t):
        if isinstance(t, test.teams.Team):
            self.team = t

Тест/teams.py:

import test.users

class Team:
    def setLeader(self, u):
        if isinstance(u, test.users.User):
            self.leader = u

from test import teams, а затем teams.Team тоже ОК, если вы хотите написать test меньше. Это все еще импортирует модуль, а не член модуля.

Кроме того, если team и user относительно просты, поместите их в один и тот же модуль. Вам не нужно следовать идиоме Java one-class-per-file. Тестирование isinstance и методы set также кричат ​​мне беспутную-Java-бородавку; в зависимости от того, что вы делаете, вам вполне может быть лучше использовать простой, не проверенный тип @property.

Ответ 2

я. Чтобы он работал, вы можете использовать отложенный импорт. Один из способов - оставить user.py самостоятельно и сменить команду team.py на:

class team:
    def setLeader(self, u):
        from test.user import user
        if issubclass(u, user.__class__):
            self.leader = u

III. Для альтернативы, почему бы не поместить команды и пользовательские классы в один и тот же файл?

Ответ 3

Плохая практика/вонючий - это следующие вещи:

  • Проба ненужного проверки типов (см. также здесь). Просто используйте объекты, которые вы получаете как пользователь/команда, и создаете исключение (или, в большинстве случаев, один поднимается без необходимости использования дополнительного кода), когда он ломается. Оставьте это, и вы кругового импорта уйти (по крайней мере на данный момент). Пока объекты, которые вы получаете, ведут себя как пользователь/команда, они могут быть чем угодно. (Duck Typing)
  • классы нижних регистров (это более или менее вопрос вкуса, но общепринятый стандарт (PEP 8) делает это по-другому
  • setter, где не нужно: вы могли бы сказать: my_team.leader=user_b и user_b.team=my_team
  • проблемы с согласованностью данных: что, если (my_team.leader.team!=my_team)?

Ответ 4

Вот что я еще не видел. Это плохая идея/дизайн, используя sys.modules напрямую? Прочитав решение @bobince, я подумал, что понял весь импортный бизнес, но потом столкнулся с проблемой, похожей на вопрос , который ссылается на этот.

Вот еще одно решение:

# main.py
from test import team
from test import user

if __name__ == '__main__':
    u = user.User()
    t = team.Team()
    u.setTeam(t)
    t.setLeader(u)

# test/team.py
from test import user

class Team:
    def setLeader(self, u):
        if isinstance(u, user.User):
            self.leader = u

# test/user.py
import sys
team = sys.modules['test.team']

class User:
    def setTeam(self, t):
        if isinstance(t, team.Team):
            self.team = t

а файл test/__init__.py пуст. Причина этого в том, что сначала импортируется test.team. В момент, когда python импортирует/считывает файл, он добавляет модуль в sys.modules. Когда мы импортируем test/user.py, модуль test.team уже будет определен, поскольку мы импортируем его в main.py.

Мне нравится эта идея для модулей, которые растут довольно большими, но есть функции и классы, которые зависят друг от друга. Предположим, что есть файл с именем util.py, и этот файл содержит много классов, которые зависят друг от друга. Возможно, мы могли бы разделить код между разными файлами, которые зависят друг от друга. Как мы обходим круглый импорт?

Ну, в файле util.py мы просто импортируем все объекты из других файлов "private", я говорю private, так как эти файлы не предназначены для прямого доступа, вместо этого мы обращаемся к ним через исходный файл:

# mymodule/util.py
from mymodule.private_util1 import Class1
from mymodule.private_util2 import Class2
from mymodule.private_util3 import Class3

Затем на каждом из других файлов:

# mymodule/private_util1.py
import sys
util = sys.modules['mymodule.util']
class Class1(object):
    # code using other classes: util.Class2, util.Class3, etc

# mymodule/private_util2.py
import sys
util = sys.modules['mymodule.util']
class Class2(object):
    # code using other classes: util.Class1, util.Class3, etc

Вызов sys.modules будет работать до тех пор, пока сначала будет импортирован mymodule.util.

Наконец, я только укажу, что это делается для того, чтобы помочь пользователям с удобочитаемостью (более короткие файлы), и поэтому я бы не сказал, что круговой импорт "по своей сути" плох. Все могло быть сделано в том же файле, но мы используем это, чтобы мы могли отделять код и не путать себя при прокрутке огромного файла.