Здравствуйте, я попал в круговую зависимость, что не рефакторизуемо, кроме удвоения кода.
У меня есть что-то вроде этого (только гораздо сложнее):
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?
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
Другой вариант - импортировать main в функцию, которая его использует:
Main.py
import sys
def parseEvnt():
import main
sys.stdout.write("evnt:")
main.parseCmd(1)
Если вы настаиваете на том, чтобы не проводить рефакторинг (что является реальным решением этой проблемы - не быть мудрым человеком), вы можете перенести проблемный импорт в свою функцию в myParser.py
import sys
def parseEvnt():
import main ## import moved into function
sys.stdout.write("evnt:")
main.parseCmd(1)
Опять же, посмотрите, сможете ли вы изменить дизайн своего кода, чтобы избежать таких взаимозависимостей.
Приведенное выше решение является своего рода хаком и не решит будущие проблемы, с которыми вы можете столкнуться из-за этой зависимости.
Циркулярный импорт следует избегать. Требуется рефакторинг, любой обходной путь, который все еще требует циклического импорта, не является хорошим решением.
При этом рефакторинг не должен быть обширным. Есть хотя бы пара довольно простых решений.
Решение 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
может сохранить эту ссылку в глобальной переменной.
Похожие вопросы
Новые вопросы
python
Python - это многопарадигмальный, динамически типизированный, многоцелевой язык программирования. Он разработан для быстрого изучения, понимания и использования, а также для обеспечения чистого и единообразного синтаксиса. Обратите внимание, что Python 2 официально не поддерживается с 01.01.2020. Тем не менее, для вопросов о Python, связанных с версией, добавьте тег [python-2.7] или [python-3.x]. При использовании варианта Python (например, Jython, PyPy) или библиотеки (например, Pandas и NumPy) включите его в теги.