Круговий (або циклічний) імпорт у Python


353

Що буде, якщо два модулі імпортують один одного?

Щоб узагальнити проблему, як щодо циклічного імпорту в Python?



1
також в якості довідника, здається, що круговий імпорт дозволений на python 3.5 (і, можливо, далі), але не 3,4 (і, мабуть, нижче).
Чарлі Паркер

4
Я використовую python 3.7.2 і все ще маю помилку виконання через кругові залежності.
Річард Уайтхед

Відповіді:


282

Про це була справді хороша дискусія на comp.lang.python минулого року. Він досить грунтовно відповідає на ваше запитання.

Імпорт насправді досить простий. Просто запам’ятайте наступне:

'import' та 'from xxx import yyy' є виконавчими операторами. Вони виконуються, коли запущена програма досягне цього рядка.

Якщо модуль відсутній у sys.modules, то імпорт створює новий запис модуля в sys.modules, а потім виконує код у модулі. Він не повертає управління модулю, що викликає, поки виконання не завершиться.

Якщо модуль існує в sys.modules, то імпорт просто повертає його, незалежно від того, завершив він виконання чи ні. Саме тому циклічний імпорт може повертати модулі, які здаються частково порожніми.

Нарешті, виконуваний скрипт запускається в модулі з іменем __main__, імпорт сценарію під власним іменем створить новий модуль, не пов'язаний з __main__.

Візьміть цю партію разом, і ви не повинні отримувати сюрпризів при імпорті модулів.


13
@meawoppl Чи можете ви розширити цей коментар, будь ласка? Наскільки конкретно вони змінилися?
Dan Schien

3
На сьогодні єдине посилання на круговий імпорт у python3 "Що нового?" сторінки розміщені в 3.5 . У ньому йдеться про те, що "круговий імпорт із відносним імпортом зараз підтримується". @meawoppl Ви знайшли ще щось, що не вказано на цих сторінках?
zezollo

4
Вони деф. не підтримується в 3.0-3.4. Або принаймні семантика успіху різна. Ось конспект, який я виявив, що не згадує 3,5 зміни. gist.github.com/datagrok/40bf84d5870c41a77dc6
meawoppl

Будь ласка, можете розширити це "Нарешті, виконуваний скрипт працює в модулі з ім'ям main. Імпорт сценарію під власним іменем створить новий модуль, не пов'язаний з основним ." Тож скажемо, що файл - це a.py, і коли він працює як головна точка входу, його тепер головним, якщо він має код, як з імпорту якоїсь змінної. Тоді той самий файл 'a.py' завантажується в таблицю модулів sys? Так це означає, що якщо він має твердити print, то він запуститься двічі? Один раз для основного файлу і знову, коли виникає імпорт?
змінна

Цій відповіді 10 років, і я хотів би модернізувати оновлення для того, щоб воно залишалося правильним у різних версіях Python, 2.x або 3.x
Fallenreaper

296

Якщо робити import fooвсередині barі import barвсередині foo, це буде добре працювати. До того часу, як щось насправді запуститься, обидва модулі будуть повністю завантажені і матимуть посилання один на одного.

Проблема полягає в тому, коли замість цього ви робите from foo import abcі from bar import xyz. Тому що зараз кожен модуль вимагає, щоб інший модуль вже був імпортований (щоб ім’я, яке ми імпортуємо, існувало), перш ніж його можна було імпортувати.


27
Здається, що так from foo import *і from bar import *буде добре працювати.
Акавал

1
Перевірте редагування публікації вище, використовуючи a.py/b.py. Він не використовує from x import y, і все-таки отримує помилку кругового імпорту
Грег Енніс

2
Це не зовсім вірно. Як і імпортувати * з, якщо ви намагаєтеся отримати доступ до елемента кругового імпорту, на верхньому рівні, тому перед тим, як скрипт завершить його виконання, у вас виникне та сама проблема. Наприклад, якщо ви встановлюєте глобальний пакет в одному пакеті з іншого, і вони включають один одного. Я робив це для створення неохайної фабрики для об'єкта в базовому класі, де цей об’єкт міг би бути одним із ряду підкласів, і використовуючий код не повинен знати про те, що він насправді створює.
AaronM

3
@Akavall Не дуже. Це імпортуватиме лише ті імена, які доступні під час виконання importоператора. Таким чином, він не помилиться, але ви не зможете отримати всі очікувані змінні.
серпня

3
Зауважте, якщо ви робите from foo import *і from bar import *, все, що виконується в foo, знаходиться на етапі ініціалізації bar, а фактичні функції в barньому ще не визначені ...
Martian2049,

100

Циклічний імпорт припиняється, але вам потрібно бути обережним, щоб не використовувати циклічно імпортні модулі під час ініціалізації модулів.

Розглянемо наступні файли:

a.py:

print "a in"
import sys
print "b imported: %s" % ("b" in sys.modules, )
import b
print "a out"

b.py:

print "b in"
import a
print "b out"
x = 3

Якщо ви виконаєте a.py, ви отримаєте наступне:

$ python a.py
a in
b imported: False
b in
a in
b imported: True
a out
b out
a out

Під час другого імпорту b.py (у другому a in) інтерпретатор Python bзнову не імпортує , оскільки він вже існує у модулі dict.

Якщо ви спробуєте отримати доступ b.xз aініціалізації модуля, ви отримаєте AttributeError.

Додайте наступний рядок до a.py:

print b.x

Потім, вихід:

$ python a.py
a in                    
b imported: False
b in
a in
b imported: True
a out
Traceback (most recent call last):
  File "a.py", line 4, in <module>
    import b
  File "/home/shlomme/tmp/x/b.py", line 2, in <module>
    import a
 File "/home/shlomme/tmp/x/a.py", line 7, in <module>
    print b.x
AttributeError: 'module' object has no attribute 'x'

Це відбувається тому, що модулі виконуються під час імпорту, і в той час, коли b.xдоступ до них, рядок x = 3ще не виконаний, що відбудеться лише після b out.


14
це значно пояснює проблему, але як щодо рішення? як ми могли правильно імпортувати та друкувати х? інше рішення вище для мене не спрацювало
mehmet

Я думаю, що ця відповідь буде корисна, якщо ви використовуєте __name__замість цього 'a'. На початку мене зовсім збентежило, чому файл буде виконуватися двічі.
Бергі

30

Оскільки інші відповіді описують, ця модель прийнятна в python:

def dostuff(self):
     from foo import bar
     ...

Що дозволить уникнути виконання оператора імпорту, коли файл імпортується іншими модулями. Тільки за умови логічної кругової залежності це не вдасться.

Більшість циркулярних імпортів насправді не є логічним круговим імпортом, а швидше викликають ImportErrorпомилки через те, як import()оцінюються висловлювання вищого рівня всього файлу при його виклику.

Цього ImportErrorsпрактично можна уникнути, якщо ви позитивно хочете, щоб ваш імпорт був зверху :

Розглянемо цей круговий імпорт:

Додаток A

# profiles/serializers.py

from images.serializers import SimplifiedImageSerializer

class SimplifiedProfileSerializer(serializers.Serializer):
    name = serializers.CharField()

class ProfileSerializer(SimplifiedProfileSerializer):
    recent_images = SimplifiedImageSerializer(many=True)

Додаток В

# images/serializers.py

from profiles.serializers import SimplifiedProfileSerializer

class SimplifiedImageSerializer(serializers.Serializer):
    title = serializers.CharField()

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()

Від David Beazleys відмінні розмовні модулі та пакети: Живи та нехай помре! - PyCon 2015 , 1:54:00ось спосіб вирішити круговий імпорт в python:

try:
    from images.serializers import SimplifiedImageSerializer
except ImportError:
    import sys
    SimplifiedImageSerializer = sys.modules[__package__ + '.SimplifiedImageSerializer']

Це намагається імпортувати, SimplifiedImageSerializerі якщо ImportErrorйого підняти, оскільки він вже імпортується, він витягне його з імпорту кеша.

PS: Ви повинні прочитати весь цей пост у голосі Девіда Бізлі.


9
ImportError не підвищується, якщо модуль вже імпортований. Модулі можна імпортувати стільки разів, скільки ви хочете, тобто "імпортувати; імпортувати;" гаразд.
Юрас

9

Тут я отримав приклад, який мене вразив!

foo.py

import bar

class gX(object):
    g = 10

bar.py

from foo import gX

o = gX()

main.py

import foo
import bar

print "all done"

У командному рядку: $ python main.py

Traceback (most recent call last):
  File "m.py", line 1, in <module>
    import foo
  File "/home/xolve/foo.py", line 1, in <module>
    import bar
  File "/home/xolve/bar.py", line 1, in <module>
    from foo import gX
ImportError: cannot import name gX

2
Як ви це виправили? Я намагаюся зрозуміти круговий імпорт, щоб вирішити власну проблему, яка дуже схожа на те, що ви робите ...
c089

12
Помилка ... Я думаю, що я вирішив свою проблему з цим неймовірно потворним злом. {{{якщо не 'foo.bar' у sys.modules: з панелі імпорту foo else: bar = sys.modules ['foo.bar']}}} Особисто я вважаю, що круговий імпорт є ВЕЛИЧЕЗНИМ попереджувальним знаком про неправильний код дизайн ...
c089

5
@ C089, або ви могли б просто рухатися import barв foo.pyдо кінця
warvariuc

5
Якщо barі fooобидва повинні використовувати gX, "найчистішим" рішенням є розміщення gXв іншому модулі, і те, і інше, fooта barімпорт цього модуля. (найчистіший в тому сенсі, що немає прихованих семантичних залежностей.)
Тім Уайлдер

2
У Тіма хороший момент. В основному це тому, що barнавіть не можна знайти gXв підніжжі. круговий імпорт сам по собі добре, але це просто gXне визначено, коли він імпортується.
Martian2049

9

Модуль a.py:

import b
print("This is from module a")

Модуль b.py

import a
print("This is from module b")

Запуск "Модуля a" виведе:

>>> 
'This is from module a'
'This is from module b'
'This is from module a'
>>> 

Він виводить ці 3 рядки, тоді як передбачалося виводити нескінченність через круговий імпорт. Що відбувається за рядком під час запуску "Модуля a", перераховано тут:

  1. Перший рядок є import b. тому він відвідає модуль b
  2. Перший рядок у модулі b є import a. тому він відвідає модуль a
  3. Перший рядок у модулі a, import bале зауважте, що цей рядок більше не буде виконуватись , оскільки кожен файл у python виконує імпортну рядок лише один раз, неважливо, де і коли він виконується. тому він перейде до наступного рядка та друкується "This is from module a".
  4. Після закінчення відвідування цілого модуля a з модуля b ми все ще знаходимося в модулі b. тому наступний рядок буде надруковано"This is from module b"
  5. Рядки модуля b виконуються повністю. тому ми повернемося до модуля a, де ми запустили модуль b.
  6. рядок імпорту b вже виконаний і більше не буде виконаний. наступний рядок буде надруковано "This is from module a"і програма буде завершена.

4

Я повністю згоден з відповіддю pythoneer тут. Але я натрапив на якийсь код, який був помилковим круговим імпортом і викликав проблеми при спробі додати одиничні тести. Тож швидко виправити його, не змінюючи все, що ви зможете вирішити проблему, виконавши динамічний імпорт.

# Hack to import something without circular import issue
def load_module(name):
    """Load module using imp.find_module"""
    names = name.split(".")
    path = None
    for name in names:
        f, path, info = imp.find_module(name, path)
        path = [path]
    return imp.load_module(name, f, path[0], info)
constants = load_module("app.constants")

Знову ж таки, це не є постійним виправленням, але може допомогти тому, хто хоче виправити помилку імпорту, не змінюючи занадто багато коду.

Ура!


3

Тут є багато чудових відповідей. Хоча зазвичай існують швидкі рішення проблеми, деякі з яких відчувають себе більш пітонічними, ніж інші, якщо у вас є розкіш робити якийсь рефакторинг, інший підхід - проаналізувати організацію вашого коду та спробувати усунути кругову залежність. Ви можете виявити, наприклад, що у вас є:

Файл a.py

from b import B

class A:
    @staticmethod
    def save_result(result):
        print('save the result')

    @staticmethod
    def do_something_a_ish(param):
        A.save_result(A.use_param_like_a_would(param))

    @staticmethod
    def do_something_related_to_b(param):
        B.do_something_b_ish(param)

Файл b.py

from a import A

class B:
    @staticmethod
    def do_something_b_ish(param):
        A.save_result(B.use_param_like_b_would(param))

У цьому випадку, просто перемістивши один статичний метод в окремий файл, скажіть c.py:

Файл c.py

def save_result(result):
    print('save the result')

дозволить видалити save_resultметод з A, і таким чином дозволить видалити імпорт A з a в b:

Повторно налаштований файл a.py

from b import B
from c import save_result

class A:
    @staticmethod
    def do_something_a_ish(param):
        A.save_result(A.use_param_like_a_would(param))

    @staticmethod
    def do_something_related_to_b(param):
        B.do_something_b_ish(param)

Відновлений файл b.py

from c import save_result

class B:
    @staticmethod
    def do_something_b_ish(param):
        save_result(B.use_param_like_b_would(param))

Підсумовуючи це, якщо у вас є інструмент (наприклад, pylint або PyCharm), який повідомляє про методи, які можуть стати статичними, просто кинути staticmethodна них декоратор може бути не найкращим способом заглушити попередження. Незважаючи на те, що метод здається пов'язаним з класом, може бути краще відокремити його, особливо якщо у вас є кілька тісно пов’язаних модулів, які можуть потребувати однакової функціональності, і ви маєте намір практикувати принципи DRY.


2

Круговий імпорт може бути заплутаним, оскільки імпорт робить дві речі:

  1. він виконує імпортований код модуля
  2. додає імпортований модуль до таблиці імпорту глобального символу модуля

Перший робиться лише один раз, тоді як останній при кожному заяві про імпорт. Круговий імпорт створює ситуацію, коли для імпорту модуля використовується імпортний з частково виконаним кодом. Як наслідок, він не побачить об’єкти, створені після оператора імпорту. Нижче показаний зразок коду.

Круговий імпорт - не найвище зло, якого слід уникати будь-якою ціною. У деяких структурах, таких як Flask, вони є цілком природними, і налаштування вашого коду для їх усунення не покращує код.

main.py

print 'import b'
import b
print 'a in globals() {}'.format('a' in globals())
print 'import a'
import a
print 'a in globals() {}'.format('a' in globals())
if __name__ == '__main__':
    print 'imports done'
    print 'b has y {}, a is b.a {}'.format(hasattr(b, 'y'), a is b.a)

b.by

print "b in, __name__ = {}".format(__name__)
x = 3
print 'b imports a'
import a
y = 5
print "b out"

a.py

print 'a in, __name__ = {}'.format(__name__)
print 'a imports b'
import b
print 'b has x {}'.format(hasattr(b, 'x'))
print 'b has y {}'.format(hasattr(b, 'y'))
print "a out"

Вихід python main.py з коментарями

import b
b in, __name__ = b    # b code execution started
b imports a
a in, __name__ = a    # a code execution started
a imports b           # b code execution is already in progress
b has x True
b has y False         # b defines y after a import,
a out
b out
a in globals() False  # import only adds a to main global symbol table 
import a
a in globals() True
imports done
b has y True, a is b.a True # all b objects are available

1

Я вирішив проблему наступним чином, і вона працює добре без помилок. Розглянемо два файли a.pyта b.py.

Я додав це a.pyі це спрацювало.

if __name__ == "__main__":
        main ()

a.py:

import b
y = 2
def main():
    print ("a out")
    print (b.x)

if __name__ == "__main__":
    main ()

b.py:

import a
print ("b out")
x = 3 + a.y

Вихід, який я отримую, - це

>>> b out 
>>> a out 
>>> 5

0

Гаразд, я думаю, що у мене досить круте рішення. Скажімо, у вас є файл aі файл b. У вас є defабо classв файл , bякий ви хочете використовувати в модулі a, але у вас є що - то ще, або def, classабо змінну з файлу , aщо вам потрібно в ухвалі або класу в файлі b. Що ви можете зробити, це внизу файлу a, після виклику функції або класу у файлі, aякий потрібен у файлі b, але перед викликом функції або класу з файлу, bякий вам потрібен для файлу a, скажімо, import b тоді, і ось ключова частина , у всіх визначеннях або класах у файлі, bякі потребують файлу defабо classз ньогоa(назвемо це CLASS), ви кажетеfrom a import CLASS

Це працює, тому що ви можете імпортувати файл bбез виконання Python будь-якого з імпортних виписок у файлі b, і, таким чином, ви уникаєте будь-якого кругового імпорту.

Наприклад:

Файл:

class A(object):

     def __init__(self, name):

         self.name = name

CLASS = A("me")

import b

go = B(6)

go.dostuff

Файл b:

class B(object):

     def __init__(self, number):

         self.number = number

     def dostuff(self):

         from a import CLASS

         print "Hello " + CLASS.name + ", " + str(number) + " is an interesting number."

Вуаля.


from a import CLASSнасправді не пропускає виконання всього коду в a.py. Так відбувається насправді: (1) Весь код у a.py запускається як спеціальний модуль "__main__". (2) На import bпочатку, код верхнього рівня в b.py запускається (визначаючи клас B), а потім управління повертається до "__main__". (3) "__main__" з часом передає контроль go.dostuff(). (4) коли dostuff () приходить import a, він запускає весь код у a.py знову , на цей раз як модуль "a"; тоді він імпортує об’єкт CLASS з нового модуля "a". Так що насправді це буде однаково добре, якби ви використовували import aбудь-де в b.py.
Маттіас Фріпп
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.