Порядок инициализации модуля Python?

Я новичок Python, исходящий из фона С++. Хотя я знаю, что это не Pythonic, чтобы попытаться найти подходящую концепцию, используя мои старые знания на С++, я думаю, что этот вопрос по-прежнему остается общим вопросом:

В С++ существует известная проблема, называемая фиаско порядка инициализации глобальной/статической переменной, из-за невозможности С++ решить, какая глобальная/статическая переменная будет инициализирована сначала в единицах компиляции, таким образом, глобальная/статическая переменная, зависящая от другой в разных единицах компиляции могут быть инициализированы раньше, чем их сопоставления зависимостей, и когда зависимые начинают использовать сервисы, предоставляемые объектом зависимости, мы будем иметь поведение undefined. Здесь я не хочу углубляться в то, как С++ решает эту проблему.:)

В мире Python я вижу использование глобальных переменных, даже в разных .py файлах, и один типичный случай использования, который я видел, это: инициализировать один глобальный объект в одном .py файле и на других .py файлах код просто бесстрашно начинает использовать глобальный объект, предполагая, что он, должно быть, был инициализирован где-то еще, что под С++, безусловно, неприемлемо для меня, из-за проблемы, указанной выше.

Я не уверен, что используемый выше пример использования - это обычная практика в Python (Pythonic), и как Python решает эту проблему с проблемой инициализации глобальной переменной вообще?

Большое спасибо!

Лин

Ответы

Ответ 1

В С++ существует известная проблема, называемая фиаско порядка инициализации глобальной/статической переменной, из-за невозможности С++ решить, какая глобальная/статическая переменная будет инициализирована сначала в единицах компиляции,

Я думаю, что этот оператор подчеркивает ключевое различие между Python и С++: в Python нет таких вещей, как разные единицы компиляции. Я имею в виду, что на С++ (как вы знаете) два разных исходных файла могут быть скомпилированы полностью независимо друг от друга, и, таким образом, если вы сравните строку в файле A и строку в файле B, вам нечего сказать вы, который будет размещен первым в программе. Это похоже на ситуацию с несколькими потоками: вы не можете сказать, будет ли конкретный оператор в потоке 1 выполняться до или после определенного оператора в потоке 2. Можно сказать, что программы на С++ скомпилированы параллельно.

Напротив, в Python выполнение начинается в начале одного файла и выполняется в определенном порядке через каждый оператор в файле, разветвляясь на другие файлы в точках, где они импортируются. Фактически, вы могли бы почти думать о директиве import как #include, и таким образом вы могли бы определить порядок выполнения всех строк кода во всех исходных файлах программы. (Ну, это немного сложнее, потому что модуль действительно выполняется только при первом импорте и по другим причинам.) Если С++-программы скомпилированы параллельно, программы Python интерпретируются последовательно.

В вашем вопросе также затрагивается более глубокое значение модулей в Python. Модуль Python - это все, что находится в одном файле .py, - это фактический объект. Все объявленное в "глобальной" области в одном исходном файле фактически является атрибутом этого объекта модуля. В Python нет истинной глобальной области. (Программисты Python часто говорят "глобальные", и на самом деле есть ключевое слово global на языке, но оно всегда действительно относится к верхнему уровню текущего модуля.) Я мог видеть, что это немного странная концепция, чтобы получить используется для перехода с фона С++. Для меня потребовалось некоторое время, чтобы перейти от Java, и в этом отношении Java намного больше похожа на Python, чем на С++. (В Java также нет глобальной области)

Я упомянул, что в Python совершенно нормально использовать переменную без понятия, была ли она инициализирована/определена или нет. Ну, может быть, это не нормально, но, по крайней мере, приемлемо при соответствующих обстоятельствах. В Python попытка использовать переменную undefined вызывает a NameError; вы не получаете произвольного поведения, как вы могли бы на C или С++, поэтому вы можете легко справиться с ситуацией. Вы можете увидеть этот шаблон:

try:
    duck.quack()
except NameError:
    pass

который ничего не делает, если duck не существует. На самом деле, что вы чаще будете видеть,

try:
    duck.quack()
except AttributeError:
    pass

который ничего не делает, если duck не имеет метода с именем quack. (AttributeError является той ошибкой, которую вы получаете, когда пытаетесь получить доступ к атрибуту объекта, но у объекта нет никакого атрибута по этому имени.) Это то, что проходит для проверки типа в Python: мы полагаем, что if все, что нам нужно, чтобы сделать утку, - это шарлатанство, мы можем просто попросить ее крякать, и если это произойдет, нам все равно, действительно ли это утка или нет. (Он называл утиную печать, -)

Ответ 2

Импорт Python запускает новые модули Python от начала до конца. Последующие импорты приводят только к копированию существующей ссылки в sys.modules, даже если она по-прежнему находится в середине импорта модуля из-за циклического импорта. Атрибуты модуля ( "глобальные переменные" на самом деле находятся в области модуля), которые были инициализированы до того, как будет существовать циклический импорт.

main.py:

import a

a.py:

var1 = 'foo'
import b
var2 = 'bar'

b.py:

import a
print a.var1 # works
print a.var2 # fails