Імпорт кругового Python?


98

Отже, я отримую цю помилку

Traceback (most recent call last):
  File "/Users/alex/dev/runswift/utils/sim2014/simulator.py", line 3, in <module>
    from world import World
  File "/Users/alex/dev/runswift/utils/sim2014/world.py", line 2, in <module>
    from entities.field import Field
  File "/Users/alex/dev/runswift/utils/sim2014/entities/field.py", line 2, in <module>
    from entities.goal import Goal
  File "/Users/alex/dev/runswift/utils/sim2014/entities/goal.py", line 2, in <module>
    from entities.post import Post
  File "/Users/alex/dev/runswift/utils/sim2014/entities/post.py", line 4, in <module>
    from physics import PostBody
  File "/Users/alex/dev/runswift/utils/sim2014/physics.py", line 21, in <module>
    from entities.post import Post
ImportError: cannot import name Post

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

Відповіді:


161

Я думаю, що відповідь jpmc26, хоча жодним чином не є неправильною , надто сильно обрушується на круговий імпорт. Вони можуть працювати чудово, якщо ви їх правильно налаштували.

Найпростіший спосіб це зробити import my_module, а не використовувати синтаксис from my_module import some_object. Перші будуть працювати майже завжди, навіть якщо my_moduleвключатимуть нас назад. Останнє працює лише в тому випадку, якщо my_objectце вже визначено в my_module, що при циклічному імпорті може бути не таким.

Щоб бути конкретним для вашої справи: спробуйте змінити дію entities/post.py, import physicsа потім звернутися до, physics.PostBodyа не просто PostBodyбезпосередньо. Аналогічно, змініть physics.pyробити, import entities.postа потім використовуйте, entities.post.Postа не просто Post.


5
Чи сумісна ця відповідь із відносним імпортом?
Джо

17
Чому це відбувається?
Хуан Пабло Сантос

4
Неправильно сказати, що fromнесинтаксис завжди буде працювати. Якщо у мене є class A(object): pass; class C(b.B): passмодулі a та class B(a.A): passмодуль b, тоді круговий імпорт все ще є проблемою, і це не буде працювати.
CrazyCasta

1
Ви маєте рацію, будь-які кругові залежності в коді верхнього рівня модулів (наприклад, базові класи оголошень класу у вашому прикладі) будуть проблемою. Це така ситуація, коли відповідь jpmc про те, що вам слід переформатувати організацію модуля, є, мабуть, на 100% правильною. Або перемістіть клас Bв модуль a, або перемістіть клас Cв модуль, bщоб ви могли розірвати цикл. Варто також відзначити , що навіть якщо тільки в одному напрямку окружності має код верхнього рівня , що беруть участь (наприклад , якщо клас Cне існує), ви могли б отримати помилку, в залежності від того, який модуль був імпортований першим іншим кодом.
Blckknght

2
@TylerCrompton: Я не впевнений, що ви маєте на увазі під "імпорт модулів повинен бути абсолютним". Циркулярний відносний імпорт може працювати, якщо ви імпортуєте модулі, а не їх вміст (наприклад from . import sibling_module, ні from .sibling_module import SomeClass). Існує ще трохи тонкощів, коли файл пакету __init__.pyбере участь у круговому імпорті, але ця проблема є рідкісною, і, ймовірно, помилкою в importреалізації. Див. Помилку Python 23447 , для якої я надіслав виправлення (яке, на жаль, виснажується).
Blckknght

51

Коли ви імпортуєте модуль (або його член) вперше, код всередині модуля виконується послідовно, як і будь-який інший код; наприклад, це не трактується інакше, як тіло функції. importПросто команда , як і будь-якого іншого (привласнення, виклик функції, def, class). Якщо припустити, що імпорт відбувається у верхній частині сценарію, ось що відбувається:

  • При спробі імпортувати Worldз world, worldсценарій виконується.
  • В worldімпорті сценарію Field, який викликає entities.fieldсценарій , щоб отримати страчена.
  • Цей процес триває, поки ви не дійдете до entities.postсценарію, оскільки ви намагалися імпортуватиPost
  • entities.postСкрипт викликає physicsмодуль буде виконуватися , оскільки він намагається імпортPostBody
  • Нарешті, physicsнамагається імпортувати Postзentities.post
  • Я ще не впевнений, чи entities.postіснує модуль у пам'яті, але це насправді не має значення. Або модуль відсутній у пам'яті, або модуль ще не має Postчлена, оскільки він не закінчив виконання для визначенняPost
  • У будь-якому випадку, виникає помилка, оскільки Postїї немає для імпорту

Так що ні, це не "робота далі в стеку викликів". Це слід стека того, де сталася помилка, а це означає, що вона помилилася при спробі імпорту Postв цей клас. Не слід використовувати циркулярне імпортування. У кращому випадку це має незначну користь (як правило, ніякої користі), і це викликає подібні проблеми. Це обтяжує будь-якого розробника, підтримуючи його, змушуючи їх ходити по яєчним шкаралупам, щоб не зламати його. Переформатуйте вашу організацію модулів.


1
Має бути isinstance(userData, Post). Незалежно від того, у вас немає вибору. Циркулярне імпортування не спрацює. Той факт, що у вас круговий імпорт, для мене є запахом коду. Це передбачає, що у вас є деякі функції, які слід перенести до третього модуля. Я не міг сказати, що, не дивлячись на цілі класи.
jpmc26,

3
@CpILL Через деякий час мені дуже спалахнув варіант. Якщо ви поки що не можете обійтись цим (через обмеження в часі або що у вас є), тоді ви можете зробити імпорт локально всередині методу, де ви його використовуєте. Тіло функції всередині defне виконується, доки функція не буде викликана, тому імпорт не відбуватиметься, поки ви насправді не викликаєте функцію. На той час imports повинні працювати, оскільки один із модулів був би повністю імпортований до дзвінка. Це абсолютно огидний злом, і він не повинен залишатися у вашій кодовій базі протягом значного періоду часу.
jpmc26,

15
Я думаю, що ваша відповідь надто важка щодо кругового імпорту. Циркулярне імпортування зазвичай працює, якщо ви робите просто, import fooа не from foo import Bar. Це тому, що більшість модулів просто визначають речі (наприклад, функції та класи), які запускаються пізніше. Модулі, які роблять важливі дії, коли ви їх імпортуєте (наприклад, скрипт, не захищений if __name__ == "__main__"), все одно можуть бути проблемою, але це не надто часто.
Blckknght 05.03.14

6
@ Blckknght Я думаю, що ти налаштовуєш себе на те, щоб витрачати час на дивні проблеми, які інші люди повинні будуть розслідувати і збивати з пантелику, якщо використовувати циркулярний імпорт. Вони змушують витрачати час на обережність, щоб не зіткнутися з ними, а на додачу - запах коду, який ваш дизайн потребує рефакторингу. Можливо, я помилився, чи технічно це можливо, але це жахливий вибір дизайну, якому рано чи пізно доведеться викликати проблеми. Чіткість і простота - це святий Грааль у програмуванні, а циркулярне імпортування порушує обидва пункти моєї книги.
jpmc26,

6
В якості альтернативи; Ви занадто розділили свою функціональність, і це є причиною циркулярного імпорту. Якщо у вас є дві речі, які весь час покладаються одна на одну ; можливо, найкраще просто помістити їх в один файл. Python - це не Java; немає причини не групувати функціональність / класи в один файл, щоб запобігти дивній логіці імпорту. :-)
Марк Рібау,

40

Щоб зрозуміти кругові залежності, потрібно пам’ятати, що Python - це, по суті, мова сценаріїв. Виконання операторів поза методами відбувається під час компіляції. Оператори імпорту виконуються так само, як виклики методів, і щоб зрозуміти їх, вам слід думати про них як про виклики методів.

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

Уявіть, у вас є два вихідні файли:

Файл X.py

def X1:
    return "x1"

from Y import Y2

def X2:
    return "x2"

Файл Y.py

def Y1:
    return "y1"

from X import X1

def Y2:
    return "y2"

Тепер припустимо, ви скомпілюєте файл X.py. Компілятор починає з визначення методу X1, а потім потрапляє до оператора імпорту в X.py. Це змушує компілятор зупинити компіляцію X.py і розпочати компіляцію Y.py. Незабаром після цього компілятор потрапляє до оператора імпорту в Y.py. Оскільки X.py вже є в таблиці модулів, Python використовує існуючу неповну таблицю символів X.py, щоб задовольнити будь-які запитувані посилання. Будь-які символи, що з’являються перед оператором імпорту в X.py, тепер містяться в таблиці символів, а будь-які символи після - ні. Оскільки X1 тепер відображається перед оператором імпорту, він успішно імпортований. Потім Python продовжує компіляцію Y.py. При цьому він визначає Y2 і завершує компіляцію Y.py. Потім він відновлює компіляцію X.py і знаходить Y2 у таблиці символів Y.py. Компіляція врешті завершує без помилки.

Щось зовсім інше трапляється, якщо ви спробуєте скомпілювати Y.py із командного рядка. Під час компіляції Y.py компілятор потрапляє до оператора імпорту, перш ніж він визначає Y2. Потім він починає компілювати X.py. Незабаром він потрапляє до оператора імпорту в X.py, який вимагає Y2. Але Y2 невизначений, тому компіляція не вдається.

Зверніть увагу, що якщо ви модифікуєте X.py для імпорту Y1, компіляція завжди буде успішною, незалежно від того, який файл ви компілюєте. Однак якщо ви зміните файл Y.py на імпорт символу X2, жоден файл не буде скомпільований.

Будь-коли, коли модуль X або будь-який модуль, імпортований X, може імпортувати поточний модуль, НЕ використовуйте:

from X import Y

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

import X
z = X.Y

Припустимо, модуль X імпортує цей модуль до того, як цей модуль імпортує X. Далі припустимо, що Y визначено в X після оператора імпорту. Тоді Y не буде визначено, коли цей модуль імпортується, і ви отримаєте помилку компіляції. Якщо цей модуль спочатку імпортує Y, ви можете це зробити. Але коли хтось із ваших колег невинно змінює порядок визначень у третьому модулі, код зламається.

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

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

#import X   (actual import moved down to avoid circular dependency)

Як правило, це погана практика, але іноді її складно уникнути.


2
Я не думаю, що компілятор або час компіляції у python взагалі немає
pkqxdd

6
Python робить є компілятор, і буде складена @pkqxdd, компіляція просто як правило , прихована від користувача. Це може трохи заплутати, але автору буде важко дати цей надзвичайно чіткий опис того, що відбувається, без жодних посилань на "незначний" час компіляції Python.
Хенк


Я спробував спробувати це на своїй машині і отримав інший результат. Виконав X.py, але отримав помилку "неможливо імпортувати ім'я" Y2 "з" Y "". Ран Y.py без проблем. Я працюю на Python 3.7.5, чи можете ви допомогти пояснити, в чому тут проблема?
xuefeng huang

18

Тим з вас, хто, як і я, прийшов до цього питання від Django, слід знати, що документи пропонують рішення: https://docs.djangoproject.com/en/1.10/ref/models/fields/#foreignkey

"... Для посилання на моделі, визначені в іншій програмі, ви можете чітко вказати модель із повною міткою програми. Наприклад, якщо модель виробника, вказана вище, визначена в іншій програмі, яка називається виробничою, вам потрібно буде використовувати:

class Car(models.Model):
    manufacturer = models.ForeignKey(
        'production.Manufacturer',
        on_delete=models.CASCADE,
)

Таке посилання може бути корисним при вирішенні циклічних залежностей імпорту між двома програмами. ... "


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

Я згоден з @MikeyE. Я прочитав кілька блогів та Stackoverflows, намагаючись виправити це за допомогою PonyORM. Там, де інші кажуть, що це погана практика, або чому б ви кодували свої класи як кругові, ORM - це саме там, де це відбувається. Оскільки багато прикладів розміщують усі моделі в одному файлі, і ми дотримуємося цих прикладів, окрім того, що використовуємо модель для кожного файлу, проблема незрозуміла, коли Python не вдається скомпілювати. Однак відповідь така проста. Як зазначив Майк, велике спасибі.
сміття80

4

Мені вдалося імпортувати модуль у межах функції (лише), яка вимагала б об'єктів з цього модуля:

def my_func():
    import Foo
    foo_instance = Foo()

який елегантний пітон
Яро

2

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

введіть тут опис зображення


0

Я використовував наступне:

from module import Foo

foo_instance = Foo()

але щоб позбутися, circular referenceя зробив наступне, і це спрацювало:

import module.foo

foo_instance = foo.Foo()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.