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

Файл с расширением .py называется модулем, а имя этого файла является именем модуля. При этом все переменные, функции и классы из одного модуля можно свободно использовать в других модулях. Если код в файле предназначен для непосредственного запуска, а не для повторного использования, то такой файл часто называют скриптом.

Набор связанных модулей, которые совместно обеспечивают определённую функциональность, называется пакетом. Он представляет собой папку, которая содержит несколько файлов с расширением .py и часто содержит специальный служебный файл __init__.py, обозначающий, что эта папка является пакетом.

Библиотека является более общим понятием, так как в программировании под ней понимают набор готового кода, который можно повторно использовать в своём проекте. Библиотека в Python может быть представлена как одним модулем, так и пакетом, который, в том числе, может содержать другие пакеты.

Импорт модулей

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

В таком случае имеет смысл собрать эти функции в один файл, например, text_tools.py, то есть создать модуль с именем text_tools.

Для примера пусть он содержит одну простую функцию count_words(), которая подсчитывает количество слов в строке:

# text_tools.py

def count_words(text: str) -> int:
    word_list = text.split() 
    return len(word_list)

Теперь, чтобы воспользоваться функционалом этого модуля в другом файле, его следует импортировать. Для этого в Python используется специальный оператор import.

Оператор import делает содержимое модуля (функции, классы, переменные) доступным для использования в текущем модуле.

Предположим, главный файл main.py находится в той же папке, что и text_tools.py:

my_project/
├── main.py
└── text_tools.py

Обычно именем main.py называют основной запускаемый файл. Он может содержать минимальный код, вызывающий логику из других модулей и пакетов, которые могут быть импортированы разными способами, каждый из которых имеет свои особенности.

Классический импорт

Загрузка всего модуля с помощью оператора import является самым распространённым способом импорта:

import имя_модуля

Эта команда загружает модуль и создает для него пространство имен, то есть систему, сопоставляющую каждому имени соответствующий объект. По умолчанию, пространство имен совпадает с именем самого модуля, и оно позволяет обратиться к любой переменной, классу или функции из модуля:

пространство_имен.имя_объекта

И если мы импортируем весь модуль text_tools.py, то обращаться к функции count_words() следует через имя этого модуля:

# main.py

import text_tools

text = "Привет, это мой новый модуль"
word_count = text_tools.count_words(text)
print(f"Слов в строке: {word_count}")
# Вывод: Слов в строке: 5

Такой способ импорта делает код однозначным, то есть вы сразу видите, откуда взялась функция count_words, что помогает избежать конфликтов имен.

Импорт с псевдонимом

Когда имя модуля очень длинное или вы планируете использовать его очень часто, рекомендуется присвоить ему более короткое имя или псевдоним с помощью ключевого слова as:

import имя_модуля as псевдоним

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

# main.py

import text_tools as tt

text = "Псевдонимы делают код короче"
word_count = tt.count_words(text)
print(f"Слов в строке: {word_count}")
# Вывод: Слов в строке: 4

Выборочный импорт

Если из большого модуля нужна всего одна или две функции  то вместо того, чтобы каждый раз писать его имя или псевдоним, можно использовать ключевое слово from. Оно указывает из какого модуля происходит импорт, а импортируемые объекты перечисляются через запятую после оператора import:

from имя_модуля import объект1, объект2,...

Тогда к таким объектам можно обращаться так, как будто они написаны в этом же файле:

from text_tools import count_words

text = "Только эта функция мне нужна"
word_count = count_words(text)
print(f"Слов в строке: {word_count}")
# Вывод: Слов в строке: 5

Однако этот способ может привести к конфликту имен. Если у вас уже есть своя функция с именем count_words(), импортированная функция ее перезапишет и код станет двусмысленным.

Импорт всего содержимого

Вместо перечисления импортируемых объектом можно просто указать символ звёздочки (*), которая импортирует всё содержимое модуля:

from имя_модуля import *

Тогда все переменные, функции и классы модуля будут доступны в текущем пространстве имен, как если бы они были там определены:

from text_tools import *

text = "Мне нужны все переменные, функции и классы"
word_count = count_words(text)
print(f"Слов в строке: {word_count}")
# Вывод: Слов в строке: 7

Однако этот способ считается плохой практикой в Python, так как делает код плохо читаемым и неоднозначным, поскольку мы не знаем, какие именно имена были импортированы, а также высока вероятность конфликтов имён, так как импортированные объекты перезапишут те, что используются в основном файле.

Импорт пакетов

Мы уже рассмотрели импорт модулей, которые находятся в той же папке, что и наш основной файл. Однако проект может содержать не только модули, но и пакеты, поэтому давайте усложним структуру проекта:

my_project/
├── main.py                    # Главный исполняемый файл проекта
|
├── text_analysis/             # Пакет для анализа текста
|   ├── __init__.py            # Делает папку text_analysis пакетом
|   └── text_tools.py          # Функции для работы с текстом
|
└── calculations/              # Пакет для всех математических вычислений
    ├── __init__.py            # Делает папку calculations пакетом
    ├── basic.py               # Функции для базовых вычислений
    └── advanced/              # Подпакет для более сложных вычислений
        ├── __init__.py        # Делает папку advanced подпакетом
        └── stats.py           # Функции для статистического анализа

Здесь модуль text_tools.py выделен в отдельный пакет text_analysis, а также добавлен новый пакет calculations для математических вычислений. В этом пакете находится модуль basic.py для базовых математических функций и подпакет для более сложных функций advanced, который, в свою очередь, содержит модуль stats.py для функций, связанных со статистикой.

Для работы со сложной структурой папок Python использует иерархию пакетов и два основных метода импорта: абсолютный и относительный.

Абсолютный импорт

Абсолютный импорт заключается в указании полного пути к модулю или пакету, начиная от корневой папки.

В Проводнике полный путь к модулю text_tools.py мог бы выглядеть как C:\tasks\my_project\text_analysis\text_tools.py. При абсолютном импорте важна только часть после названия проекта (папки, в которой находится запускаемый файл), а обратные слэши заменяются на точки:

import text_analysis.text_tools

text = "Теперь модуль находится в пакете"
word_count = text_analysis.text_tools.count_words(text)
print(f"Слов в строке: {word_count}")
# Вывод: Слов в строке: 5

Здесь работают те же правила, что и при импорте модуля, поэтому вместо длинного пути удобнее использовать псевдоним:

import text_analysis.text_tools as tt

text = "Мне нужен псевдоним"
word_count = tt.count_words(text)
print(f"Слов в строке: {word_count}")
# Вывод: Слов в строке: 3

Также можно импортировать не весь пакет, а только одну функцию из модуля text_tools.py:

from text_analysis.text_tools import count_words

text = "Из пакета мне нужна только одна функция"
word_count = count_words(text)
print(f"Слов в строке: {word_count}")
# Вывод: Слов в строке: 7

При запуске файла main.py, Python автоматически добавляет путь к папке, в которой находится запускаемый файл, в список sys.path. Эта переменная представляет собой список путей поиска модулей и инициализируется при запуске программы.

Когда интерпретатор Python встречает строку import имя_пакета или имя_модуля, он в порядке очереди проходит по всем путям в списке sys.path, пока не найдет файл или папку, соответствующую имени. Если модуль или пакет не был найден, то вызывается исключение ModuleNotFoundError. Также к этому исключению приведёт нарушение иерархии импорта, если файл с импортом запущен из подпапки, а не из корневой папки проекта.

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

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

И если при абсолютном импорте мы идём вниз от корневой папки до нужного модуля, то при относительном импорте, наоборот, поднимаемся наверх с текущего модуля до нужного модуля.

Для относительного импорта используются точки в качестве префикса, а количество точек указывает на сколько уровней мы поднимаемся от текущего модуля и теоретически не ограничено:

  • from .имя_модуля import ... – импорт модуля, который находится на одном уровне с текущим файлом.
  • from ..имя_модуля import ... – импорт модуля, который находится на один уровень выше текущего модуля.

Также если абсолютный импорт использует как конструкции import имя_модуля, так и from имя_модуля import ..., то относительный импорт поддерживает только выборочный импорт from .имя_модуля import ....

Например, ранее мы импортировали функцию mean() из модуля basic.py в модуль stats.py во вложенном пакете advanced:

# calculations/advanced/stats.py

from calculations.basic import mean

Относительный импорт позволяет записать это как:

# calculations/advanced/stats.py

from ..basic import mean

Здесь .. означает подняться на один уровень выше, в папку calculations, где находится модуль basic.py.

Особенности запуска модулей

Файл с расширением .py может выполнять две принципиально разные роли: модуль (импортирован) или программа (запущен напрямую).

Python автоматически создает и инициализирует специальную встроенную переменную __name__, значение которой зависит от способа запуска модуля:

  • __name__ == "__main__" – модуль запущен напрямую.
  • __name__ == "имя_модуля" – модуль импортирован другим модулем.

Условие if __name__ == "__main__": позволяет создать блок кода, который будет выполнен только в том случае, если файл запущен как основная программа, и будет проигнорирован, если файл импортируется как модуль.

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

Например, модуль basic.py может содержать не только функцию mean() для расчёта среднего арифметического, но и пример работы этой функции, который будет выведен на экран, только если этот модуль запущен напрямую:

# calculations/basic.py

def mean(data):
    numbers = list(map(float, data))
    return sum(numbers) / len(numbers)


# Этот код выполнится только при прямом запуске basic.py
if __name__ == "__main__":
    result = mean([1, 2, 3, 4, 5])
    print(f"Среднее арифметическое чисел 1, 2, 3, 4 и 5: {result}")

Этот подход позволяет создавать модули, которые содержат полезные функции и при этом имеют встроенный механизм для их тестирования или демонстрации при прямом запуске, не вмешиваясь в основной ход программы при импорте.

Примеры

Пример 1. Расчёт точки безубыточности

Проект имеет следующую структуру:

project/
├── main.py
└── finance /
    ├── __init__.py
    └── stats.py

Функция get_break_even() для расчёта точки окупаемости находится во вложенном модуле stats.py в пакете finance:

# finance/stats.py

def get_break_even(
    big_cost: int | float, 
    price: int | float, 
    cost_per_item: int | float
) -> int | None:
    """
    Рассчитывает точку окупаемости - сколько единиц товара нужно продать,
    чтобы полностью вернуть big_cost.

    Параметры:
        big_cost: Большая первоначальная трата (напр., оборудование).
        price: Цена продажи одной единицы товара.
        cost_per_item: Себестоимость материалов для одной единицы товара.

    Возвращает:
        Минимальное количество единиц для окупаемости или None.
    """
    # Чистый доход с одной проданной единицы
    profit_per_item = price - cost_per_item
    
    if profit_per_item <= 0:
        return None
        
    # Делим общие расходы на чистый доход с единицы и округляем вверх
    return round(big_cost / profit_per_item + 0.5)

Эта функция импортируется в запускаемый файл main.py с помощью абсолютного импорта и используется для расчёта точки безубыточности производства булочек:

# main.py

# main.py

from finance.stats import get_break_even

big_cost = 120000  # Всего потрачено на организацию производства булочек
price = 120  # Цена продажи одной булочки  
cost_per_item = 60  # Себестоимость одной булочки 
break_even_point = get_break_even(big_cost, price, cost_per_item)

print(f"Нужно продать {break_even_point} булочек")

Вывод:

Нужно продать 2000 булочек

Пример 2. Настройки генерации отчёта

Проект имеет следующую структуру:

reporting/
├── __init__.py
├── settings.py
└── generator.py

Модуль settings.py хранит настройки для создания отчёта:

# reporting/settings.py

report_title = "Ежемесячный финансовый отчет"
separator = "=" * 40

Переменные report_title с названием отчёта и separator с разделителем используются для создания заголовка отчёта в функции generate_header() в модуле generator.py:

  # reporting/generator.py

from .settings import report_title, separator

def generate_header(month: str) -> str:
    """Генерирует заголовок отчёта.
    
    Параметры:
        month: Название месяца.
        
    Возвращает:
        Строка с заголовком для отчёта.
    """
    header = f"{separator}\n{report_title} - {month.upper()}\n{separator}"
    return header


if __name__ == "__main__":
    # Код для демонстрации, если generator.py запущен напрямую
    print(generate_header("Ноябрь"))

Если модуль generator.py запущен напрямую, то выполняется код в блоке if __name__ == "__main__": и на экран выводится пример заголовка отчёта за ноябрь.

Вывод:

========================================
Ежемесячный финансовый отчет - НОЯБРЬ
========================================

Итоги

  • Модуль – это файл с расширением .py. Все переменные, функции и классы из одного модуля можно использовать в других модулях.
  • Скрипт – это файл с расширением .py, предназначенный для запуска.
  • Пакет – это набор связанных модулей, которые совместно обеспечивают определённую функциональность.
  • Библиотека – это набор готового кода, который можно использовать повторно.
  • Значение переменной __name__ для каждого модуля зависит от способа его запуска: оно равно "__main__", если модуль запущен напрямую, или совпадает с именем модуля, если он импортирован другим модулем.
Способы импорта модулей и пакетов

Импорт

Описание

import имя_модуля

Импорт модуля

from имя_модуля import...

Выборочный импорт элементов модуля

from имя_модуля import *

Импорт всего содержимого модуля

from имя_пакета.имя_модуля import ...

Абсолютный импорт модуля через указание полного пути к нужному модулю от корневого пакета

from . имя_модуля import ...

Относительный импорт модуля, который находится на то же уровне с текущим файлом

from .. имя_модуля import ...

Относительный импорт модуля, который находится на один уровень выше текущего модуля

Задания для самопроверки

1. Как классический импорт (import имя_модуля) помогает избежать конфликтов имен?

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

2. Какие значения принимает встроенная переменная __name__ в модуле, если он запущен напрямую и, если он импортирован другим модулем?

Значение переменной __name__ равно "__main__", если модуль запущен напрямую, и совпадает с именем модуля, если он импортирован другим модулем.

3. Создайте в одной папке файлы data_utils.py и main.py. В модуле data_utils.py напишите функцию clean_text(text), которая принимает строку text и возвращает эту же строку без лишних пробелом по краям и преобразованную в верхний регистр. В запускаемом файле main.py импортируйте весь модуль data_utils.py и вызовите функцию clean_text() для строки " Данные бывают РаЗнЫе " и выведите результат на экран.

# data_utils.py

def clean_text(text: str) -> str:
    return text.strip().upper()


# main.py

import data_utils

input_string = "  Данные бывают РаЗнЫе "
result = data_utils.clean_text(input_string)
print(result)
# Вывод: ДАННЫЕ БЫВАЮТ РАЗНЫЕ

4. Измените файл main.py из предыдущего задания и импортируйте модуль data_utils.py через псевдоним du. Вызовите функцию clean_data() для строки " Чёрные, белые, красные " и выведите результат на экран.

# main.py

import data_utils as du

input_string2 = "   Чёрные, белые, красные     "
result = du.clean_text(input_string2)
print(result)
# Вывод: ЧЁРНЫЕ, БЕЛЫЕ, КРАСНЫЕ

5. Создайте модуль my_math.py с функцией add(a, b), которая принимает два числа a и b и возвращает результат сложения этих чисел. В этом же модуле создайте функцию subtract(a, b), которая принимает два числа a и b и возвращает результат вычитания числа b из числа a. В файле main.py используйте команду вида from ... import ... для импорта функции add() и выведите на экран результат её вызова для чисел 10 и 5.

# my_math.py

def add(a: float, b: float) -> float:
    return a + b


def subtract(a: float, b: float) -> float:
    return a - b


# main.py

from my_math import add

result = add(10, 5)
print(result)
# Вывод: 15