Модуль повторно импортируется, если импортируется по другому пути
В большом приложении, над которым я работаю, несколько человек по-разному импортируют одни и те же модули, например import x или from y import x, побочные эффекты которого x импортируются дважды и могут привести к очень тонким ошибкам, если кто-то полагается на глобальные атрибуты
например предположим, что у меня есть пакет mypakcage с тремя файлами mymodule.py, main.py и init.py
содержимое mymodule.py
l = []
class A(object): pass
содержимое main.py
def add(x):
from mypackage import mymodule
mymodule.l.append(x)
print "updated list",mymodule.l
def get():
import mymodule
return mymodule.l
add(1)
print "lets check",get()
add(1)
print "lets check again",get()
это печатает
updated list [1]
lets check []
updated list [1, 1]
lets check again []
потому что теперь есть два списка в двух разных модулях, аналогично класс А отличается. Для меня это выглядит достаточно серьезно, потому что сами классы будут обрабатываться по-разному, например, нижеприведенный код выводит False
def create():
from mypackage import mymodule
return mymodule.A()
def check(a):
import mymodule
return isinstance(a, mymodule.A)
print check(create())
Вопрос:
Есть ли способ избежать этого? кроме принудительного исполнения, этот модуль должен быть импортирован в одну сторону. Разве это не может быть обработано механизмом импорта python, я видел несколько ошибок, связанных с этим, в коде django и в других местах.
2 ответа
Я могу повторить это, только если main.py - это файл, который вы на самом деле запускаете. В этом случае вы получите текущий каталог main.py по пути sys. Но у вас, очевидно, также установлен системный путь, чтобы mypackage можно было импортировать.
В этом случае Python не поймет, что mymodule и mypackage.mymodule - это один и тот же модуль, и вы получите этот эффект. Это изменение иллюстрирует это:
def add(x):
from mypackage import mymodule
print "mypackage.mymodule path", mymodule
mymodule.l.append(x)
print "updated list",mymodule.l
def get():
import mymodule
print "mymodule path", mymodule
return mymodule.l
add(1)
print "lets check",get()
add(1)
print "lets check again",get()
$ export PYTHONPATH=.
$ python mypackage/main.py
mypackage.mymodule path <module 'mypackage.mymodule' from '/tmp/mypackage/mymodule.pyc'>
mymodule path <module 'mymodule' from '/tmp/mypackage/mymodule.pyc'>
Но добавьте еще один основной файл в каталог currect:
realmain.py:
from mypackage import main
и результат другой:
mypackage.mymodule path <module 'mypackage.mymodule' from '/tmp/mypackage/mymodule.pyc'>
mymodule path <module 'mypackage.mymodule' from '/tmp/mypackage/mymodule.pyc'>
Поэтому я подозреваю, что у вас есть основной файл Python в пакете. И в этом случае решение состоит в том, чтобы не делать этого.:-)
Пространство имен каждого модуля импортируется только один раз. Проблема в том, что вы импортируете их по-другому. На первом вы импортируете из глобального пакета, а на втором вы делаете локальный, неупакованный import
, Python видит модули как разные. Первый импорт внутренне кэшируется как mypackage.mymodule
а второй как mymodule
только.
Чтобы решить эту проблему, всегда используйте абсолютный импорт. То есть всегда указывайте абсолютные пути импорта вашего модуля из пакета верхнего уровня и далее:
def add(x):
from mypackage import mymodule
mymodule.l.append(x)
print "updated list",mymodule.l
def get():
from mypackage import mymodule
return mymodule.l
Помните, что ваша точка входа (файл, который вы запускаете, main.py
) также должен быть за пределами пакета. Когда вы хотите, чтобы код точки входа находился внутри пакета, обычно вместо этого вы запускаете небольшой скрипт. Пример:
runme.py
вне упаковки:
from mypackage.main import main
main()
И в main.py
вы добавляете:
def main():
# your code
Я нахожу этот документ Jp Calderone отличным советом о том, как (не) структурировать ваш проект на Python. После этого у вас не будет проблем. Обратите внимание на bin
папка - это вне пакета. Я воспроизведу весь текст здесь:
Структура файловой системы проекта Python
Делать:
- Назовите каталог, связанный с вашим проектом. Например, если ваш проект называется "Twisted", назовите каталог верхнего уровня для его исходных файлов.
Twisted
, Когда вы делаете релизы, вы должны включать суффикс номера версии:Twisted-2.5
,- создать каталог
Twisted/bin
и положите туда свои исполняемые файлы, если они у вас есть. Не дают им.py
расширение, даже если они исходные файлы Python. Не помещайте в них какой-либо код, кроме импорта и вызова основной функции, определенной где-то еще в ваших проектах.- Если ваш проект может быть выражен как один исходный файл Python, поместите его в каталог и назовите его как-нибудь связанный с вашим проектом. Например,
Twisted/twisted.py
, Если вам нужно несколько исходных файлов, вместо этого создайте пакет (Twisted/twisted/
с пустымTwisted/twisted/__init__.py
) и поместите в него ваши исходные файлы. Например,Twisted/twisted/internet.py
,- Поместите ваши модульные тесты в подпакет вашего пакета (обратите внимание - это означает, что указанная выше опция с единственным исходным файлом Python была хитростью - для ваших модульных тестов всегда нужен хотя бы один другой файл). Например,
Twisted/twisted/test/
, Конечно, сделайте пакет сTwisted/twisted/test/__init__.py
, Поместите тесты в файлы, такие какTwisted/twisted/test/test_internet.py
,- добавлять
Twisted/README
и тwisted/setup.py
объяснить и установить программное обеспечение, соответственно, если вы чувствуете себя хорошо.Не:
- поместите ваш источник в каталог под названием
src
или жеlib
, Это затрудняет запуск без установки.- поместите свои тесты вне вашего пакета Python. Это затрудняет запуск тестов с установленной версией.
- создать пакет, который имеет только
__init__.py
а затем положить весь свой код в__init__.py
, Просто сделайте модуль вместо пакета, это проще.- попробуйте придумать магические взломы, чтобы Python мог импортировать ваш модуль или пакет без необходимости добавления пользователем каталога, содержащего его, в путь импорта (либо через
PYTHONPATH
или какой-то другой механизм). Вы не будете правильно обрабатывать все случаи, и пользователи будут злиться на вас, если ваше программное обеспечение не работает в их среде.