Написание моего первого модуля на Python было запутанным опытом. Как это обычно бывает, когда я тестировал его в интерактивном Python REPL, в первой версии оказались баги (во второй и третьей тоже были 😉).

Хорошо — подумал я — я просто исправлю модуль и повторно импортирую его.

Но оказалось, что вызов from my_module import my_function не обновил код! my_function все еще была ошибка, которую я только что исправил! Я перепроверил, изменил ли я правильный файл, снова импортировал его, и все равно ничего. Оказывается, как любезно объяснил StackOverflow, нельзя просто повторно импортировать модуль. Если вы уже импортировали модуль ( import a_module) или функцию ( from a_module import a_function) в сеанс Python и пытаетесь импортировать их снова, ничего не произойдет. Неважно, используете ли вы стандартный Python REPL или IPython.

Как работает импорт в Python?

Оказывается, по соображениям эффективности, когда вы импортируете модуль в интерактивный сеанс Python, интерпретатор Python выполняет два шага:

  1. Во-первых, он проверяет, не закэширован ли уже модуль в словаре sys.module.
  2. И только если его нет, он фактически импортирует модуль.

Это означает, что если вы уже импортировали модуль (или импортировали другой модуль, который ссылается на этот) и попытаетесь импортировать его снова, Python проигнорирует этот запрос. Подробнее о том, как работает импорт, вы можете прочитать в документации.

Итак, если я не могу повторно импортировать модуль, значит ли это, что мне нужно каждый раз перезапускать Python? Не совсем, это было бы очень неудобно.

Как повторно импортировать модуль?

Самый простой способ — выйти из интерактивного сеанса и начать его заново. Это прекрасно работает, если вы не заботитесь о сохранении данных, которые уже есть в вашем сеансе, таких как написанные вами функции и вычисленные вами переменные. Но обычно вы не хотите перезапускать REPL, поэтому есть способы получше.

Поскольку мы знаем, что интерпретатор сначала будет искать модуль в словаре sys.modules, мы можем просто удалить наш модуль из этого словаря. И это будет работать в большинстве случаев, но есть некоторые предостережения. Если на ваш модуль есть ссылка из другого модуля, есть шанс, что вы все равно не сможете его повторно импортировать. Так что не делай этого. Существует лучший способ.

Рекомендуемое решение — использовать функцию importlib.reload. Эта функция предназначена именно для реимпорта модулей, которые уже были импортированы ранее. Чтобы перезагрузить модуль, вам нужно запустить:

import importlib
importlib.reload(my_module)

Вот как вы можете повторно импортировать модуль в Python. И если вы не используете IPython, на этом ваши варианты заканчиваются. Но у пользователей IPython есть и другие интересные решения этой проблемы.

%бегать

Если вам не нужно на самом деле «импортировать» ваш модуль, и все, что вам нужно, это запустить некоторые функции, определенные в файле, вместо этого вы можете выполнить этот файл. Он будет запускать все команды, как если бы вы копировали и вставляли их в сеанс IPython. Вы можете повторно запускать файл столько раз, сколько захотите, и он всегда будет обновлять все функции. Запустить файл в IPython чрезвычайно просто:

%run my_file.py
# You can even skip the ".py" extension:
%run my_file

Я немного схитрил, когда сказал, что эта опция недоступна в стандартном Python REPL. Это так, но для этого требуется больше ввода:

exec(open("./my_file.py").read())

Честно говоря, если бы мне пришлось набирать все это, я мог бы просто использовать importlib.reload вместо этого.

Все эти варианты хороши, но если вы так же плохо, как я, когда дело доходит до написания кода, и вы делаете много ошибок, то это означает много перезагрузки. И печатать это importlib.reload/%run/exec... раздражает. Было бы здорово, если бы был способ автоматически перезагружать модуль? Что ж, IPython действительно может это сделать!

%autoreload в помощь

Еще один магический метод в IPython связан с перезагрузкой модулей. Это называется %autoreload. По умолчанию он не включен, поэтому вам нужно загрузить его как расширение:

%load_ext autoreload

Теперь вы можете включить автозагрузку:

%autoreload 2

И каждый раз, когда вы выполняете какой-либо код, IPython повторно импортирует все модули, чтобы убедиться, что вы используете самые последние возможные версии.

Есть 3 варианта конфигурации, которые вы можете установить:

  • %autoreload 0 - отключает автоперезагрузку. Это значение по умолчанию.
  • %autoreload 1 — будут автоматически перезагружаться только те модули, которые были импортированы с помощью функции %aimport (например, %aimport my_module). Это хороший вариант, если вы хотите автоматически перезагружать только выбранный модуль.
  • %autoreload 2 - автоперезагрузка всех модулей. Отличный способ значительно упростить написание и тестирование модулей.

Отлично, есть оговорки? Я нашел 3 второстепенных:

  • IPython с включенным %autoreload будет немного медленнее. IPython довольно умен в отношении того, что перезагружать. Он проверит временные метки модификации модулей и сравнит их со временем, когда они были импортированы. Но эта проверка (и, в конечном итоге, повторный импорт измененных модулей) все равно займет некоторое время. Он не будет настолько медленным, чтобы вы могли это почувствовать (если только у вас нет модулей, импорт которых занимает несколько секунд), но, очевидно, он будет работать быстрее, если вы отключите автоматическую перезагрузку.
  • Как указано в документации, %autoreload не на 100% надежен и может иметь некоторые неожиданные последствия. Я никогда не замечал никаких проблем, но вы могли бы, поэтому я просто предупреждаю вас.
  • Вы должны убедиться, что у вас нет синтаксических ошибок в ваших модулях, когда вы запускаете команды IPython. Я часто начинаю писать какой-то код в файл и в середине команды переключаюсь на IPython, чтобы быстро что-то протестировать. И когда я выполняю какой-то код в IPython, он попытается повторно импортировать файл, который я только что изменил (тот, что с наполовину написанной командой), и выдаст SyntaxError. Хорошо то, что после ошибки вы все равно получите вывод команды, которую вы выполнили. Так что для меня это мелкая неприятность, а не реальная проблема. Вы можете легко решить эту проблему, запустив два сеанса IPython — один для тестирования модуля (с включенным %autoreload), а другой — для запуска некоторых случайных команд и поиска информации в документации.

Вот как %autoreload работает на практике:



Так что, если вы еще не знаете %autoreload, попробуйте в следующий раз, когда будете работать над модулем на Python!

Первоначально опубликовано на https://switowski.com 1 октября 2019 г.