Импорты

Что такое модуль?

подробнее

Модуль - это файл с расширением .py.

Зачем нужны модули?

  • Разделение логики: Код не лежит одной «простыней», а разбит на тематические файлы (например, db.py, utils.py).
  • Повторное использование: Один раз написанный модуль можно подключать в любые другие проекты через import.
  • Пространство имен: Функции с одинаковыми именами в разных модулях не конфликтуют (например, math.sqrt и cmath.sqrt).

Что такое пакет?

подробнее

Пакет - это способ объединить несколько модулей в одну общую структуру (папку). Если модуль - это файл, то пакет - это директория с модулями.

Когда выполняется __init__.py? Как много раз он будет выполнен?

подробнее

Файл __init__.py выполняется ровно один раз при первом импорте пакета (или любого модуля внутри него) в рамках текущего сеанса работы программы.
При повторном вызове import в любой части программы Python просто берет уже готовый объект из памяти, не перечитывая файл __init__.py.

Как работает import my_module?

подробнее
  • Python ищет файл в списке путей sys.path.
  • Превращает .py в байт-код .pyc (папка __pycache__).
  • Код модуля выполняется сверху вниз один раз. Все созданные объекты сохраняются в словаре sys.modules.
  • Если импортировать модуль второй раз, Python просто возьмет его из кэша sys.modules, не выполняя код заново.

В чём разница между import foo и from foo import bar?

подробнее

import foo — импорт «контейнера».
from foo import bar — импорт «содержимого». Копируется ссылка на объект. Если в foo переменной bar присвоят новый объект, то локальная копия продолжит смотреть на старую ссылку.

  • Чистота имен:
    • import foo - безопаснее. Вы точно знаете, что bar пришел из foo, и он не перекроет вашу локальную переменную с тем же именем.
    • from foo import bar- короче, но если у вас уже есть переменная bar, будет коллизия.
  • Объем загрузки:
    • В обоих случаях Python полностью исполняет код модуля foo и загружает его в память (sys.modules).
  • Динамическое обновление:
    • Если import foo и внутри foo изменится значение bar, то увидеть изменение можно через foo.bar.
    • Если from foo import bar, то создаётся локальная копия ссылки. Если оригинал в модуле переназначат, локальная переменная bar всё равно будет указывать на старый объект.

Пример динамического обновления:

# foo.py
bar = "Оригинал"

def update():
    global bar
    bar = "Новое значение"  # Переназначаем переменную внутри модуля

# main.py
from foo import bar, update

print(bar)  # Выведет: "Оригинал"
update()    # Внутри foo.py bar теперь "Новое значение"
print(bar)  # Выведет: "Оригинал" (СВЯЗЬ РАЗОРВАНА!)

# main2.py
import foo

print(foo.bar)  # Выведет: "Оригинал"
foo.update()    # Внутри foo.py bar изменился
print(foo.bar)  # Выведет: "Новое значение" (ВСЁ ОКТУАЛЬНО)

В чём разница между from foo import bar и from foo import *?

подробнее
  • from foo import bar - импортируется только конкретный объект bar
  • from foo import * - импортируются все объекты из модуля (кроме тех, что начинаются с _)

Как контролировать что передастся при import *?

Для этого нужно создать список строк __all__, где каждая строка - это имя (переменной, функции или класса).

# foo.py
__all__ = ['bar']  # При import * импортируется ТОЛЬКО bar

bar = 1
secret = 2  # Это не импортируется через *

Относительные импорты.

подробнее

Относительный импорт - это способ импортировать модули внутри одного пакета, указывая путь «от текущего файла» с помощью точек.

  • from . import models — из этой же папки.
  • from ..utils import tools — из папки уровнем выше.
  • from .. import config — из корня пакета.

Почему относительные импорты не работают в модуле который запущен напрямую?

У модуля внутри пакета имя выглядит как package.subpacket.module. Точки в имени помогают Python понять, где «верх», а где «низ».
Python определяет путь импорта на основе переменной __name__. Если модуль запущен напрямую (например python main.py), имя __name__ всегда равно "__main__". В этом имени нет точек, поэтому Python «не знает», где находится этот файл относительно других, и не может отсчитать путь вверх или вбок.

Зачем используется as при импорте?

подробнее

Ключевое слово as (псевдоним) используется для переименования импортируемого модуля или объекта прямо в текущем коде.

  • Борьба с коллизиями (конфликтами)
    Если у в коде уже есть функция open но нужна еще одна из модуля os

    from os import open as os_open
    
  • Сокращение длинных путей

    import tensorflow as tf
    import pandas as pd
    
  • Улучшение читаемости

    from src.utils.data_loaders import load_config as get_cfg
    
  • Унификация кода (Interface matching)
    Чтобы код работал с разными библиотеками под одним именем.

    try:
        import simplejson as json
    except ImportError:
        import json
    

Когда возникает проблема циклического импорта и как её решить?

подробнее

Циклический импорт возникает, когда два модуля пытаются импортировать друг друга одновременно: A импортирует B, а B в это же время импортирует A.

Как решить?

  • Импорт внутри функции (Local Import).
    Перенеси import из начала файла внутрь функции, где он реально нужен. Модуль загрузится только в момент вызова функции, когда оба файла уже инициализированы.

    # A.py
    def do_something():
        from B import func_b  # Импорт внутри, а не сверху
        func_b()
    
  • Объединение модулей.
    Объединить A и B в один модуль.
  • Вынос общей логики.
    Создай третий модуль C.py, вынеси туда общие данные/функции, и пусть и A, и B импортируют их оттуда.
  • Использование import foo вместо from foo import bar.
    Иногда (но не всегда) полные импорты модулей помогают, так как Python успевает создать объект модуля в sys.modules до того, как начнет искать в нем конкретные атрибуты.

Что такое if __name__ == "__main__"?

подробнее

if __name__ == "__main__" - это защитный механизм, который разделяет код на библиотечный (функции, классы) и исполняемый (скрипт). А также позволяет использовать один и тот же файл и как самостоятельную программу, и как модуль для других программ без побочных эффектов при импорте.

У каждого модуля в Python есть встроенная переменная __name__. Ее значение зависит от того, как запущен файл:

  • Если файл запущен напрямую (через консоль): __name__ всегда равно "__main__".
  • Если файл импортирован другим модулем: __name__ равно имени файла (модуля).

Пример:

def add(a, b):
    return a + b

# Этот блок выполнится ТОЛЬКО если запустить файл напрямую
if __name__ == "__main__":
    print("Запуск теста:")
    print(add(2, 2))

Что такое from __future__ import?

подробнее

from __future__ import - это механизм обратной совместимости, который позволяет использовать функции из будущих версий Python в текущей.
Когда разработчики Python вводят фичу, которая ломает старый код (например, меняет логику деления или строк), они сначала добавляют её в модуль __future__.

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