Модуль повторно импортируется, если импортируется по другому пути

В большом приложении, над которым я работаю, несколько человек по-разному импортируют одни и те же модули, например 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 или какой-то другой механизм). Вы не будете правильно обрабатывать все случаи, и пользователи будут злиться на вас, если ваше программное обеспечение не работает в их среде.
Другие вопросы по тегам