Здравствуйте, я попал в круговую зависимость, что не рефакторизуемо, кроме удвоения кода.

У меня есть что-то вроде этого (только гораздо сложнее):

MyParser.py :

import sys
import main                      #comment this to make it runnable
def parseEvnt():
    sys.stdout.write("evnt:")
    main.parseCmd(1)             #comment this to make it runnable

Tbl.py :

import myParser
tblx = {
       1:("cmd",),
       2:("evnt",myParser.parseEvnt),
       }

Main.py :

import tbl
def parseCmd(d):
    print(tbl.tblx[d][0])
data=[1,2]
for d in data:
    if(d<2):
        parseCmd(d)
    else:
        fce = tbl.tblx[d][1]
        fce()    

Очевидная ошибка, которую я получаю:

File "D:\Data\vbe\workspace\marsPython\testCircular2\main.py", line 1, in <module>
    import tbl
  File "D:\Data\vbe\workspace\marsPython\testCircular2\tbl.py", line 1, in <module>
    import myParser
  File "D:\Data\vbe\workspace\marsPython\testCircular2\myParser.py", line 2, in <module>
    import main
  File "D:\Data\vbe\workspace\marsPython\testCircular2\main.py", line 7, in <module>
    parseCmd(d)
  File "D:\Data\vbe\workspace\marsPython\testCircular2\main.py", line 3, in parseCmd
    print(tbl.tblx[d][0])
AttributeError: module 'tbl' has no attribute 'tblx'

В C я думаю, что просто сказал бы по объявлению в tbl.py, что есть функция parseEvnt(). Мне не нужно было бы включать myParser, и не было бы циркулярного включения.

В питоне я не знаю как это сделать.

Я читаю несколько тем, и всегда есть какой-то мудрец, рекомендующий рефакторинг. Но в этом случае parseCmd() нужно увидеть tblx, который должен видеть parseEvnt() (если не объявлено функции), а parseEvnt() нужно вызвать parseCmd() (потому что {{X5 }} содержит запуск cmd, и я не хочу удваивать код cmd декодирования).

Есть ли способ, как заставить его работать в Python?

2
Vit Bernatik 18 Дек 2015 в 22:33

4 ответа

Лучший ответ

Вы можете часто избегать циклических зависимостей, если модули не пытаются использовать данные друг друга до тех пор, пока весь импорт не будет завершен - на практике это означает обращение к пространству имен (from module import something запрещено) и использование только другие модули внутри функций и методов (нет mystuff = module.mystuff в глобальном пространстве). Это связано с тем, что при запуске импорта python помещает имя модуля в sys.modules и больше не пытается импортировать этот модуль.

У вас возникли проблемы, потому что когда вы запускаете main.py, python добавляет __main__ к sys.modules. Когда код наконец добрался до import main, в списке модулей не было «главного», и поэтому main.py был снова импортирован ... и его код верхнего уровня попытался запустить.

Давайте изменим ваш контрольный пример и добавим несколько операторов печати, чтобы сообщить, когда произойдет импорт.

< Сильный > myParser.py

print('  + importing myParser')
import sys

print('import parsecmd')
import parsecmd
def parseEvnt():
    sys.stdout.write("evnt:")
    parsecmd.parseCmd(1)

< Сильный > tbl.py

print('  + importing tbl')
print('import myParser')
import myParser
tblx = {
       1:("cmd",),
       2:("evnt",myParser.parseEvnt),
       }

Parsecmd.py (новый)

print('  + importing parsecmd')
print('import tbl')
import tbl
def parseCmd(d):
    print(tbl.tblx[d][0])

main.py

print('running main.py')

print('import parsecmd')
import parsecmd

if __name__ == "__main__":
    data=[1,2]
    for d in data:
        if(d<2):
            parsecmd.parseCmd(d)
        else:
            fce = parsecmd.tbl.tblx[d][1]
            fce()    

Когда я запускаю его, я получаю

running main.py
import parsecmd
  + importing parsecmd
import tbl
  + importing tbl
import myParser
  + importing myParser
import parsecmd                     <-- didn't reimport parsecmd
cmd
evnt:cmd
2
tdelaney 18 Дек 2015 в 20:41

Другой вариант - импортировать main в функцию, которая его использует:

Main.py

import sys

def parseEvnt():
    import main
    sys.stdout.write("evnt:")
    main.parseCmd(1)     
2
Vit Bernatik 18 Дек 2015 в 22:12

Если вы настаиваете на том, чтобы не проводить рефакторинг (что является реальным решением этой проблемы - не быть мудрым человеком), вы можете перенести проблемный импорт в свою функцию в myParser.py

import sys

def parseEvnt():
    import main  ## import moved into function
    sys.stdout.write("evnt:")
    main.parseCmd(1)

Опять же, посмотрите, сможете ли вы изменить дизайн своего кода, чтобы избежать таких взаимозависимостей.

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

2
Aravinda 18 Дек 2015 в 20:03

Циркулярный импорт следует избегать. Требуется рефакторинг, любой обходной путь, который все еще требует циклического импорта, не является хорошим решением.

При этом рефакторинг не должен быть обширным. Есть хотя бы пара довольно простых решений.

Решение 1. Переместите общие функции в общий модуль.

Поскольку вы хотите использовать parseCmd из нескольких мест, переместите его в отдельный файл. Таким образом, main.py и myParser.py могут импортировать функцию.

Решение 2: сделать главный проход parseCmd в parseEvnt.

Во-первых, заставьте parseEvnt принять аргумент, чтобы сообщить ему, какую функцию запустить:

# myParser.py
import sys
def parseEvnt(parseCmd):
    sys.stdout.write("evnt:")
    parseCmd(1) 

Затем, когда вы вызываете myParser.parseEvnt, передайте ссылку на main.parseCmd:

# main.py:

...
else:
    fce = tbl.tblx[d][1]
    fce(parseCmd) 

Есть и другие способы сделать то же самое. Например, вы можете добавить метод «configure» в myParser, а затем main.py вызвать метод configure и передать ссылку на его parseCmd. Затем метод configure может сохранить эту ссылку в глобальной переменной.

2
Bryan Oakley 18 Дек 2015 в 20:09