модуль повторно импортируется, если импортируется по другому пути - PullRequest
8 голосов
/ 22 сентября 2009

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

4 голосов
/ 22 сентября 2009

Каждое пространство имен модуля импортируется только один раз. Проблема в том, что вы импортируете их по-другому. На первом вы импортируете из глобального пакета, а на втором вы делаете локальный, неупакованный 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 и T wisted/setup.py, чтобы объяснить и установить программное обеспечение, соответственно, если тебе хорошо.

Не

  • поместите ваш источник в каталог называется src или lib. Это делает это сложно запустить без установки.
  • положить ваши тесты за пределами вашего Python пакет. Это затрудняет запуск тестирует установленную версию.
  • создать пакет, который имеет только __init__.py а затем положи все свои код в __init__.py. Просто сделай модуль вместо пакета, это проще.
  • попробуй придумать магические взломы, чтобы Python мог импортировать ваш модуль или пакет без попросив пользователя добавить каталог содержащий его к пути импорта (либо через PYTHONPATH, либо через механизм). Вы не будете правильно обрабатывать все случаи и пользователи получат зол на тебя, когда твое программное обеспечение не работает в их среде.
3 голосов
/ 22 сентября 2009

Я могу повторить это, только если 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 в пакете. И в этом случае решение состоит в том, чтобы этого не делать. : -)

...