Организация Python проектов с помощью модулей

Раздел: Основы Python -> Организация кода

Модуль в Python представляет собой файл с расширением .py, содержащий определения функций, классов и переменных. Использование модулей позволяет логически разделить код, упростить его повторное использование и поддержку. Далее рассмотрены основные способы создания модулей, их особенности и типичные проблемы.

Основные способы создания модулей в Python

Базовый способ: один файл .py - наиболее простой и часто используемый подход. Достаточно создать файл с произвольным именем (например, my_tools.py) и поместить в него нужные функции, классы или переменные. Затем файл можно импортировать в другом скрипте с помощью инструкции import my_tools или from my_tools import имя.

# my_tools.py
def add(a, b):
    return a + b

class Calculator:
    def multiply(self, x, y):
        return x * y

PI = 3.14159

Python сделать модуль (создание модуля в python)

# main.py
import my_tools

print(my_tools.add(2, 3))
calc = my_tools.Calculator()
print(calc.multiply(4, 5))
print(my_tools.PI)

Python project main py (структура проекта python с main.py)

5
20
3.14159

Цель использования: небольшой проект или повторное использование нескольких функций в разных файлах.

Пошаговые действия:

  1. Создать файл my_tools.py и написать в нём код.
  2. Сохранить файл в том же каталоге, где находится главный скрипт.
  3. В другом файле написать import my_tools и обращаться к элементам через точку.

Типичные проблемы и их решения:

  • Модуль не найден (ImportError): убедитесь, что файл модуля находится в текущем рабочем каталоге или в одном из каталогов, перечисленных в sys.path. Можно добавить путь с помощью sys.path.append('путь').
  • Конфликт имён: при импорте нескольких модулей с одинаковыми именами возможна перезапись. Используйте псевдонимы: import my_tools as mt.
  • Случайное выполнение кода при импорте: весь код верхнего уровня исполняется один раз. Если модуль содержит тестовый код, оберните его в if __name__ == '__main__':.

Как объединить несколько файлов в один модульный пакет?

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

# Структура каталогов:
# math_ops/
#   __init__.py
#   basic.py
#   advanced.py

# math_ops/basic.py
def sum_all(*args):
    return sum(args)

# math_ops/advanced.py
import math
def sqrt_sum(a, b):
    return math.sqrt(a + b)

# main.py
from math_ops import basic
from math_ops.advanced import sqrt_sum

print(basic.sum_all(1, 2, 3))
print(sqrt_sum(4, 5))
6
3.0

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

  • Проблема: пустой или отсутствующий __init__.py в Python 3.3+ - пакет может работать и без него (namespace packages), но для обычного пакета рекомендуется явно создавать файл. Если в __init__.py есть код, он выполняется при первом импорте пакета.
  • Ошибка импорта подмодуля: проверьте, что все модули находятся в правильных каталогах и не содержат синтаксических ошибок.

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

С помощью конструкции if __name__ == '__main__': можно управлять поведением модуля: код под этой проверкой выполняется только при прямом запуске файла, но не при импорте.

# utility.py
def greet(name):
    return f'Привет, {name}!'

if __name__ == '__main__':
    print(greet('Мир'))
# main.py
import utility
print(utility.greet('Пользователь'))
# При запуске main.py:
Привет, Пользователь!
# При запуске utility.py:
Привет, Мир!

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

Типичная ошибка: если забыть про условие if __name__ == '__main__', то при импорте выполнится весь код, включая тестовый вывод или нежелательные действия. Решение - всегда оборачивать выполняемый код в эту проверку.

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

Внутри пакета удобно использовать относительные импорты. Точка . означает текущий пакет, две точки .. - родительский пакет.

# Структура:
# package/
#   __init__.py
#   module_a.py
#   subpackage/
#     __init__.py
#     module_b.py

# package/module_a.py
def func_a():
    return 'A'

# package/subpackage/module_b.py
from ..module_a import func_a  # относительный импорт
def func_b():
    return func_a() + 'B'

Цель использования: организация кода внутри пакета без жёсткой привязки к имени пакета (облегчает переименование).

Проблема: относительные импорты не работают, если модуль запускается напрямую (как скрипт), а не импортируется как часть пакета. Ошибка ImportError: attempted relative import with no known parent package. Решение - запускать файл через python -m package.subpackage.module_b или перевести на абсолютные импорты.

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

Если файл модуля расположен не в текущем каталоге и не в стандартных путях, можно временно добавить его путь в sys.path.

# Структура:
# /home/user/project/
#   main.py
#   /home/user/libs/
#     external.py

# main.py
import sys
sys.path.append('/home/user/libs')
import external
print(external.some_function())

Цель использования: подключение внешних библиотек или общего кода, расположенного вне проекта.

  • Проблема 1: жёстко заданный путь нарушает переносимость. Используйте относительные пути относительно самого скрипта: sys.path.append(os.path.join(os.path.dirname(__file__), 'libs')).
  • Проблема 2: повторное добавление одного и того же пути. Решение - проверять наличие перед добавлением: if path not in sys.path: sys.path.append(path).

Как ограничить список импортируемых имён при from module import *?

Модуль может определить список __all__, который содержит имена, доступные для импорта через звёздочку.

# restrict.py
__all__ = ['public_func']

def public_func():
    return 'доступно'

def _private_func():
    return 'скрыто'
# main.py
from restrict import *
print(public_func())
try:
    print(_private_func())
except NameError as e:
    print('Ошибка:', e)
доступно
Ошибка: name '_private_func' is not defined

Цель использования: контроль публичного API модуля, защита внутренних функций.

Ошибка: при добавлении новой функции в модуль забывают добавить её имя в __all__. Это не вызовет ошибку, но функция не будет импортирована через *. Решение - регулярно обновлять __all__.

Расширенные примеры создания модулей

1. Модуль с несколькими функциями, классами и константами

Пример
# geometry.py
import math


def circle_area(radius):
    return math.pi * radius ** 2


def rectangle_area(a, b):
    return a * b


class Triangle:
    def __init__(self, base, height):
        self.base = base
        self.height = height

    def area(self):
        return 0.5 * self.base * self.height


PI = math.pi
Пример
# main.py
import geometry as geo
from geometry import Triangle, circle_area

print('Площадь круга радиус 5:', geo.circle_area(5))
t = Triangle(3, 4)
print('Площадь треугольника:', t.area())
print('Число PI:', geo.PI)
Площадь круга радиус 5: 78.53981633974483
Площадь треугольника: 6.0
Число PI: 3.141592653589793

2. Пакет с подмодулями и абсолютными/относительными импортами

Пример
# Структура:
# math_pack/
#   __init__.py
#   arith.py
#   trig.py
#   tests/
#     __init__.py
#     test_arith.py

# math_pack/arith.py
def add(a, b):
    return a + b

# math_pack/trig.py
import math
def sin_deg(angle):
    return math.sin(math.radians(angle))

# math_pack/__init__.py
from .arith import *
from .trig import *
__all__ = ['add', 'sin_deg']

# math_pack/tests/test_arith.py
from ..arith import add  # относительный импорт
def test_add():
    assert add(2, 3) == 5
    print('Тест пройден')

if __name__ == '__main__':
    test_add()
Пример
# main.py
import math_pack
print(math_pack.add(10, 20))
print(math_pack.sin_deg(90))
30
1.0

3. Модуль как скрипт с обработкой аргументов командной строки

Пример
# converter.py
import sys


def celsius_to_fahrenheit(c):
    return c * 9 / 5 + 32


if __name__ == '__main__':
    if len(sys.argv) != 2:
        print('Использование: python converter.py <температура в Цельсиях>')
        sys.exit(1)
    try:
        c = float(sys.argv[1])
        print(f'{c}°C = {celsius_to_fahrenheit(c)}°F')
    except ValueError:
        print('Ошибка: введите число')
        sys.exit(1)
$ python converter.py 100
100.0°C = 212.0°F
$ python converter.py
Использование: python converter.py <температура в Цельсиях>

4. Использование sys.path для импорта из нестандартного каталога

Пример
# /tmp/utils/helper.py
def get_status():
    return 'OK'

# /home/user/script.py
import sys
import os

# Путь относительно текущего файла
utils_path = os.path.join(os.path.dirname(__file__), '..', 'tmp', 'utils')
sys.path.append(os.path.abspath(utils_path))

import helper
print(helper.get_status())
OK

5. Модуль с __all__ и динамическим импортом

Пример
# dynamic.py
__all__ = ['visible']

visible = 'я видимый'
hidden = 'я скрытый'

def list_available():
    return __all__
Пример
# main.py
from dynamic import *
print(visible)
# print(hidden)  # NameError
print('Доступные имена:', list_available())
я видимый
Доступные имена: ['visible']

Создание модуля в Python - comments

En
Python сделать модуль (python)