Python & AI Tutorials Logo
Программирование Python

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

По мере того как ваши программы на Python становятся больше, хранить весь код в одном файле становится непрактично. Вам захочется организовать связанные функции(function), классы(class) и переменные(variable) в отдельные файлы, которые можно будет повторно использовать в разных программах. Система модулей(module) и пакетов(package) в Python даёт именно такую возможность — эффективно организовывать, делиться и повторно использовать код.

В этой главе мы рассмотрим, как работает система импорта(import) Python, как создавать и использовать собственные модули, а также как организовывать несколько модулей в пакеты. Мы также изучим специальную переменную __name__, которая позволяет писать файлы, работающие и как импортируемые модули, и как автономные скрипты.

22.1) Что такое модули и как работает import

Понимание модулей

Модуль(module) — это просто файл Python, содержащий определения и инструкции. Любой файл .py, который вы создаёте, является модулем. Когда вы пишете функцию в файле с именем calculator.py, этот файл становится модулем с именем calculator, который могут использовать другие файлы Python.

Модули служат нескольким важным целям:

  • Повторное использование кода: напишите функцию один раз и используйте её в нескольких программах
  • Организация: группируйте связанную функциональность вместе
  • Управление пространством имён(namespace): держите имена раздельно, чтобы избегать конфликтов
  • Сопровождаемость: небольшие, сфокусированные файлы проще понимать и изменять

Давайте создадим простой модуль, чтобы увидеть, как это работает. Создайте файл с именем greetings.py:

python
# greetings.py
def say_hello(name):
    """Return a friendly greeting."""
    return f"Hello, {name}!"
 
def say_goodbye(name):
    """Return a farewell message."""
    return f"Goodbye, {name}. See you soon!"
 
# Переменная уровня модуля
default_greeting = "Welcome"

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

Инструкция import

Чтобы использовать код из модуля, вы импортируете(import) его. Инструкция import сообщает Python загрузить модуль и сделать доступным его содержимое. Создайте другой файл в той же директории с именем main.py:

python
# main.py
import greetings
 
message = greetings.say_hello("Alice")
print(message)  # Output: Hello, Alice!
 
farewell = greetings.say_goodbye("Bob")
print(farewell)  # Output: Goodbye, Bob. See you soon!
 
print(greetings.default_greeting)  # Output: Welcome

Когда вы запускаете main.py, Python выполняет инструкцию import greetings. Вот что происходит «за кулисами»:

Нет

Да

import greetings

greetings
уже импортирован?

Искать greetings.py

Выполнить greetings.py
сверху вниз

Создать объект модуля
с именем 'greetings'

Сохранить функции и переменные
как атрибуты

Сделать 'greetings' доступным
в текущем пространстве имён

Использовать существующий
объект модуля

Важно: Python выполняет код модуля только при первом импорте в программе. Последующие импорты в той же программе повторно используют уже загруженный модуль. Это предотвращает дублирующее выполнение и экономит время.

Доступ к содержимому модуля

После импорта модуля вы обращаетесь к его содержимому с помощью точечной нотации(dot notation): module_name.item_name. Это похоже на то, как мы обращаемся к методам строк, например text.upper(), или к методам списков(list), например numbers.append(), как мы изучали в главах 5 и 14.

python
import greetings
 
# Доступ к функциям
result = greetings.say_hello("Charlie")
 
# Доступ к переменным
greeting = greetings.default_greeting
 
# Можно даже проверить, что находится в модуле
print(dir(greetings))  # Выводит список всех имён, определённых в модуле

Точечная нотация ясно показывает, откуда берётся каждое имя. Когда вы видите greetings.say_hello(), вы сразу понимаете, что эта функция происходит из модуля greetings.

Путь поиска модулей

Когда вы пишете import greetings, как Python находит greetings.py? Python ищет модули в определённом порядке:

  1. Текущая директория: директория, содержащая скрипт, который вы запускаете
  2. PYTHONPATH: директории, указанные в переменной окружения PYTHONPATH (если задана)
  3. Стандартная библиотека: встроенные директории модулей Python
  4. Site-Packages: сторонние пакеты, установленные через pip

Вы можете увидеть путь поиска Python, просмотрев sys.path:

python
import sys
 
for path in sys.path:
    print(path)

Вывод (пример — ваши пути будут отличаться в зависимости от системы и установки Python):

/home/user/projects/myproject
/usr/lib/python3.11
/usr/lib/python3.11/lib-dynload
/usr/local/lib/python3.11/site-packages

Первый путь в выводе — это текущая рабочая директория. Python сначала ищет в этой директории, поэтому он может находить модули в той же папке.

Имена модулей и имена файлов

Имя модуля — это имя файла без расширения .py. Если ваш файл — string_utils.py, имя модуля — string_utils. Имена модулей должны следовать правилам идентификаторов Python (как мы изучали в главе 3):

  • начинаться с буквы или подчёркивания
  • содержать только буквы, цифры и подчёркивания
  • не быть ключевыми словами Python
python
# Корректные имена модулей (и имена файлов)
import data_processor      # data_processor.py
import user_auth          # user_auth.py
import _internal_helpers  # _internal_helpers.py
 
# Некорректно — вызовет ошибки
# import 2d_graphics       # Can't start with digit
# import my-module         # Hyphens not allowed
# import class             # 'class' is a keyword

Частая ошибка: перекрытие модулей стандартной библиотеки

Будьте осторожны и не называйте свои модули так же, как модули стандартной библиотеки. Если вы создадите файл random.py в директории проекта, Python импортирует ваш файл вместо стандартного модуля random, что приведёт к запутанным ошибкам:

python
# Ваш файл: random.py
def my_function():
    return 42
 
# Другой файл в вашем проекте
import random
print(random.randint(1, 6))  # ERROR! Your random.py doesn't have randint()

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

Что происходит при импорте

Давайте рассмотрим, что на самом деле происходит, когда вы импортируете модуль. Создайте файл с именем demo_module.py:

python
# demo_module.py
print("Module is being loaded!")
 
def greet():
    print("Hello from demo_module")
 
print("Module loading complete!")

Теперь импортируйте его:

python
# test_import.py
print("Before import")
import demo_module
print("After import")
 
demo_module.greet()

Output:

Before import
Module is being loaded!
Module loading complete!
After import
Hello from demo_module

Обратите внимание, что инструкции print() в demo_module.py выполняются во время импорта. Это демонстрирует, что импортирование модуля запускает весь его код верхнего уровня. Определения функций сохраняются для последующего использования, но любой код вне функций выполняется сразу.

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

python
import demo_module  # Первый импорт — выполняет код модуля
import demo_module  # Второй импорт — использует кэшированный модуль
import demo_module  # Третий импорт — всё ещё использует кэшированный модуль

Output:

Module is being loaded!
Module loading complete!

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

22.2) Разные способы импорта: import, from и as

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

Базовая инструкция import

Базовая инструкция import, которую мы уже видели, загружает весь модуль целиком:

python
import math
 
result = math.sqrt(16)
print(result)  # Output: 4.0
 
pi_value = math.pi
print(pi_value)  # Output: 3.141592653589793

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

Импорт конкретных имён с помощью from

Иногда вам нужны только один-два элемента из модуля. Инструкция from позволяет импортировать конкретные имена прямо в ваше пространство имён:

python
from math import sqrt, pi
 
result = sqrt(25)  # No 'math.' prefix needed
print(result)  # Output: 5.0
 
print(pi)  # Output: 3.141592653589793

Теперь вы можете использовать sqrt и pi напрямую без префикса math.. Это удобно, когда вы часто используете эти имена.

Давайте посмотрим ещё один пример с нашим модулем greetings:

python
# Using from import
from greetings import say_hello
 
message = say_hello("Diana")  # Direct access
print(message)  # Output: Hello, Diana!
 
# However, say_goodbye is not available since we didn't import it
# say_goodbye("Diana")  # NameError: name 'say_goodbye' is not defined

Вы можете импортировать несколько имён в одной инструкции:

python
from greetings import say_hello, say_goodbye, default_greeting
 
print(say_hello("Eve"))      # Output: Hello, Eve!
print(say_goodbye("Frank"))  # Output: Goodbye, Frank!
print(default_greeting)      # Output: Welcome

Импорт со звёздочкой (и почему его стоит избегать)

Python позволяет импортировать всё из модуля с помощью *:

python
from math import *
 
print(sqrt(9))   # Output: 3.0
print(cos(0))    # Output: 1.0
print(pi)        # Output: 3.141592653589793

Это импортирует все публичные имена из модуля (имена, не начинающиеся с подчёркивания). Хотя это кажется удобным, в общем случае считается плохой практикой, потому что:

  1. Загрязнение пространства имён: вы точно не знаете, какие имена импортируете
  2. Конфликты имён: импортированные имена могут перезаписать ваши собственные переменные
  3. Читаемость: читатели кода не могут понять, откуда берутся имена
python
# Проблемный пример
from math import *
 
# Позже в вашем коде...
def sqrt(x):
    """Your own square root function."""
    return x ** 0.5
 
# Какой sqrt вы используете? Ваш или из math?
result = sqrt(16)  # Confusing!

Лучшая практика: импортируйте конкретные имена или используйте базовую инструкцию import. Избегайте from module import *, кроме интерактивных сессий, где вы экспериментируете.

Переименование импортов с помощью as

Иногда имена модулей или функций длинные, или вы хотите избежать конфликтов имён. Ключевое слово as позволяет создать псевдоним(alias):

python
import math as m
 
result = m.sqrt(36)
print(result)  # Output: 6.0

Это особенно полезно для модулей с длинными именами или при следовании распространённым соглашениям:

python
import datetime as dt
 
today = dt.date.today()
print(today)  # Output: 2025-12-19 (or current date)

Вы также можете переименовывать конкретные импорты:

python
from math import sqrt as square_root
 
result = square_root(49)
print(result)  # Output: 7.0

Это полезно при конфликтах имён:

python
from math import sqrt as math_sqrt
 
def sqrt(x):
    """Custom square root with input validation."""
    if x < 0:
        return None
    return math_sqrt(x)
 
print(sqrt(25))   # Output: 5.0 (your function)
print(sqrt(-4))   # Output: None (your function)

Комбинирование стилей импорта

Вы можете смешивать разные стили импорта в одном файле:

python
import math
from datetime import date, time
from random import randint as random_int
 
# Use math with prefix
radius = 5
area = math.pi * radius ** 2
 
# Use date and time directly
today = date.today()
current_time = time(14, 30)
 
# Use renamed function
dice_roll = random_int(1, 6)

Выбор подходящего стиля импорта

Вот руководство по выбору:

Используйте import module, когда:

  • вам нужно несколько элементов из модуля
  • вы хотите максимальную ясность, откуда берутся имена
  • имя модуля короткое и понятное

Используйте from module import name, когда:

  • вам нужен только один или два конкретных элемента
  • имена уникальны и вряд ли будут конфликтовать
  • вы будете использовать эти имена часто

Используйте import module as alias, когда:

  • имя модуля очень длинное
  • вы следуете распространённой конвенции (например, import numpy as np)
  • вам нужно избежать конфликтов с другими модулями

Избегайте from module import * в production-коде:

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

Давайте рассмотрим полный пример, демонстрирующий хорошие практики импорта:

python
# data_processor.py
import math
from statistics import mean, median
from datetime import datetime as dt
 
def calculate_statistics(numbers):
    """Calculate various statistics for a list of numbers."""
    if not numbers:
        return None
    
    avg = mean(numbers)
    mid = median(numbers)
    std_dev = math.sqrt(sum((x - avg) ** 2 for x in numbers) / len(numbers))
    
    return {
        'mean': avg,
        'median': mid,
        'std_dev': std_dev,
        'timestamp': dt.now()
    }
 
# Проверка функции
data = [10, 20, 30, 40, 50]
stats = calculate_statistics(data)
print(f"Mean: {stats['mean']}")      # Output: Mean: 30.0
print(f"Median: {stats['median']}")  # Output: Median: 30
print(f"Std Dev: {stats['std_dev']:.2f}")  # Output: Std Dev: 14.14

Этот пример показывает:

  • import math для полного модуля (возможно, позже мы будем использовать и другие функции math)
  • from statistics import mean, median для конкретных функций, которые мы используем часто
  • from datetime import datetime as dt для модуля, который часто импортируют с псевдонимом

22.3) Обзор распространённых модулей стандартной библиотеки Python

Python поставляется с богатой стандартной библиотекой(standard library) — коллекцией модулей, которые предоставляют решения типичных задач программирования. Эти модули всегда доступны; вам не нужно устанавливать ничего дополнительно. Понимание того, что доступно в стандартной библиотеке, помогает избегать «изобретения велосипеда».

Модуль math

Модуль math предоставляет математические функции, выходящие за рамки базовой арифметики:

python
import math
 
# Тригонометрические функции
angle_rad = math.radians(45)  # Преобразовать градусы в радианы
print(math.sin(angle_rad))    # Output: 0.7071067811865476
print(math.cos(angle_rad))    # Output: 0.7071067811865475
 
# Округление и модуль числа
print(math.ceil(4.2))   # Output: 5 (округление вверх)
print(math.floor(4.8))  # Output: 4 (округление вниз)
print(math.fabs(-7.5))  # Output: 7.5 (абсолютное значение как float)
 
# Экспонента и логарифмы
print(math.exp(2))      # Output: 7.38905609893065 (e^2)
print(math.log(100))    # Output: 4.605170185988092 (натуральный логарифм)
print(math.log10(100))  # Output: 2.0 (десятичный логарифм)
 
# Константы
print(math.pi)  # Output: 3.141592653589793
print(math.e)   # Output: 2.718281828459045

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

Модуль random

Модуль random генерирует псевдослучайные числа и делает случайные выборки:

python
import random
 
# Случайные целые числа
dice = random.randint(1, 6)  # Случайное целое от 1 до 6 (включительно)
print(f"Dice roll: {dice}")
 
# Случайные числа с плавающей точкой
probability = random.random()  # Случайное число float от 0.0 до 1.0
print(f"Probability: {probability:.4f}")
 
# Случайный выбор из последовательности
colors = ['red', 'blue', 'green', 'yellow']
chosen_color = random.choice(colors)
print(f"Chosen color: {chosen_color}")
 
# Перемешать список на месте
deck = ['A', 'K', 'Q', 'J', '10']
random.shuffle(deck)
print(f"Shuffled deck: {deck}")
 
# Случайная выборка без повторений
lottery_numbers = random.sample(range(1, 50), 6)
print(f"Lottery numbers: {sorted(lottery_numbers)}")

Output (example - will vary due to randomness):

Dice roll: 4
Probability: 0.7382
Chosen color: green
Shuffled deck: ['Q', 'A', '10', 'K', 'J']
Lottery numbers: [7, 15, 23, 31, 38, 42]

Модуль datetime

Модуль datetime обрабатывает даты и время:

python
from datetime import date, time, datetime, timedelta
 
# Текущая дата и время
today = date.today()
now = datetime.now()
print(f"Today: {today}")  # Output: Today: 2025-12-19
print(f"Now: {now}")      # Output: Now: 2025-12-19 14:30:45.123456
 
# Создание конкретных дат и времени
birthday = date(1990, 5, 15)
meeting_time = time(14, 30)
appointment = datetime(2025, 12, 25, 10, 0)
 
print(f"Birthday: {birthday}")          # Output: Birthday: 1990-05-15
print(f"Meeting: {meeting_time}")       # Output: Meeting: 14:30:00
print(f"Appointment: {appointment}")    # Output: Appointment: 2025-12-25 10:00:00
 
# Арифметика дат с timedelta
tomorrow = today + timedelta(days=1)
next_week = today + timedelta(weeks=1)
print(f"Tomorrow: {tomorrow}")    # Output: Tomorrow: 2025-12-20
print(f"Next week: {next_week}")  # Output: Next week: 2025-12-26
 
# Извлечение компонентов
print(f"Year: {today.year}")      # Output: Year: 2025
print(f"Month: {today.month}")    # Output: Month: 12
print(f"Day: {today.day}")        # Output: Day: 19

Модуль os

Модуль os предоставляет функциональность операционной системы. Мы подробно рассмотрим это в главе 26, а пока вот предварительный обзор:

python
import os
 
# Текущая рабочая директория
current_dir = os.getcwd()
print(f"Current directory: {current_dir}")
 
# Список файлов в директории
files = os.listdir('.')
print(f"Files: {files[:3]}")  # Показать первые 3 файла
 
# Проверить, существует ли путь
exists = os.path.exists('myfile.txt')
print(f"File exists: {exists}")
 
# Объединить компоненты пути (работает в разных ОС)
file_path = os.path.join('data', 'users', 'profile.txt')
print(f"Path: {file_path}")  # Output: data/users/profile.txt (or data\users\profile.txt on Windows)

Модуль sys

Модуль sys предоставляет системно-зависимые параметры и функции:

python
import sys
 
# Информация о версии Python
print(f"Python version: {sys.version}")
print(f"Version info: {sys.version_info}")
 
# Информация о платформе
print(f"Platform: {sys.platform}")  # Output: linux, darwin, win32, etc.
 
# Максимальный размер целого числа
print(f"Max int: {sys.maxsize}")

Модуль statistics

Модуль statistics предоставляет функции для статистических вычислений:

python
import statistics
 
grades = [85, 92, 78, 90, 88, 95, 82]
 
# Меры центральной тенденции
avg = statistics.mean(grades)
mid = statistics.median(grades)
mode_val = statistics.mode([1, 2, 2, 3, 3, 3, 4])
 
print(f"Mean: {avg}")      # Output: Mean: 87.14285714285714
print(f"Median: {mid}")    # Output: Median: 88
print(f"Mode: {mode_val}") # Output: Mode: 3
 
# Разброс
std_dev = statistics.stdev(grades)
variance = statistics.variance(grades)
 
print(f"Standard deviation: {std_dev:.2f}")  # Output: Standard deviation: 5.90
print(f"Variance: {variance:.2f}")           # Output: Variance: 34.81

Модуль collections

Модуль collections предоставляет специализированные типы контейнеров. Мы рассмотрим это подробнее в главе 39, а пока небольшой пример:

python
from collections import Counter, defaultdict
 
# Counter — подсчёт вхождений
text = "hello world"
letter_counts = Counter(text)
print(letter_counts)  # Output: Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
print(letter_counts['l'])  # Output: 3
 
# defaultdict — словарь со значениями по умолчанию
word_lists = defaultdict(list)
word_lists['fruits'].append('apple')
word_lists['fruits'].append('banana')
word_lists['vegetables'].append('carrot')
print(dict(word_lists))  # Output: {'fruits': ['apple', 'banana'], 'vegetables': ['carrot']}

Как найти больше модулей стандартной библиотеки

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

python
# Посмотреть все доступные модули (это займёт немного времени)
help('modules')
 
# Получить справку по конкретному модулю
import math
help(math)
 
# Посмотреть, что есть в модуле
import random
print(dir(random))

Документация Python (https://docs.python.org/3/library/) содержит исчерпывающую информацию о каждом модуле стандартной библиотеки. По мере накопления опыта вы обнаружите, какие модули наиболее полезны для вашей работы.

22.4) Создание и использование собственных модулей

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

Создание простого модуля

Давайте создадим модуль для работы с оценками студентов. Создайте файл с именем grade_calculator.py:

python
# grade_calculator.py
"""Module for calculating and analyzing student grades."""
 
def calculate_average(grades):
    """Calculate the average of a list of grades."""
    if not grades:
        return 0
    return sum(grades) / len(grades)
 
def get_letter_grade(numeric_grade):
    """Convert a numeric grade to a letter grade."""
    if numeric_grade >= 90:
        return 'A'
    elif numeric_grade >= 80:
        return 'B'
    elif numeric_grade >= 70:
        return 'C'
    elif numeric_grade >= 60:
        return 'D'
    else:
        return 'F'
 
def find_highest(grades):
    """Find the highest grade in a list."""
    if not grades:
        return None
    return max(grades)
 
def find_lowest(grades):
    """Find the lowest grade in a list."""
    if not grades:
        return None
    return min(grades)
 
# Константы уровня модуля
PASSING_GRADE = 60
HONOR_ROLL_THRESHOLD = 90

Теперь создайте другой файл, чтобы использовать этот модуль:

python
# student_report.py
import grade_calculator
 
# Результаты тестов студента
test_scores = [85, 92, 78, 88, 95]
 
# Рассчитать статистику
average = grade_calculator.calculate_average(test_scores)
letter = grade_calculator.get_letter_grade(average)
highest = grade_calculator.find_highest(test_scores)
lowest = grade_calculator.find_lowest(test_scores)
 
# Сформировать отчёт
print("Student Grade Report")
print("=" * 40)
print(f"Test Scores: {test_scores}")
print(f"Average: {average:.1f}")
print(f"Letter Grade: {letter}")
print(f"Highest Score: {highest}")
print(f"Lowest Score: {lowest}")
 
# Проверить попадание в список отличников
if average >= grade_calculator.HONOR_ROLL_THRESHOLD:
    print("Status: HONOR ROLL!")
elif average >= grade_calculator.PASSING_GRADE:
    print("Status: Passing")
else:
    print("Status: Needs Improvement")

Output:

Student Grade Report
========================================
Test Scores: [85, 92, 78, 88, 95]
Average: 87.6
Letter Grade: B
Highest Score: 95
Lowest Score: 78
Status: Passing

Документация модуля

Обратите внимание на docstring в верхней части grade_calculator.py. Этот docstring уровня модуля описывает, что делает модуль. Он отображается, когда кто-то использует help():

python
import grade_calculator
help(grade_calculator)

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

Переменные и константы уровня модуля

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

python
# config.py
"""Application configuration settings."""
 
# Настройки базы данных
DB_HOST = "localhost"
DB_PORT = 5432
DB_NAME = "myapp"
 
# Настройки приложения
MAX_LOGIN_ATTEMPTS = 3
SESSION_TIMEOUT = 1800  # seconds
DEBUG_MODE = False
 
# Пути к файлам
DATA_DIR = "/var/data"
LOG_DIR = "/var/log"
 
# Флаги возможностей
ENABLE_CACHING = True
ENABLE_LOGGING = True

Использование конфигурации из модуля:

python
# app.py
import config
 
def connect_database():
    """Connect to the database using config settings."""
    print(f"Connecting to {config.DB_HOST}:{config.DB_PORT}")
    print(f"Database: {config.DB_NAME}")
    
    if config.DEBUG_MODE:
        print("DEBUG: Connection details logged")
 
def check_login_attempts(attempts):
    """Check if login attempts exceed the limit."""
    if attempts >= config.MAX_LOGIN_ATTEMPTS:
        print(f"Too many attempts! Maximum is {config.MAX_LOGIN_ATTEMPTS}")
        return False
    return True
 
connect_database()
print(check_login_attempts(2))  # Output: True
print(check_login_attempts(4))  # Output: Too many attempts! Maximum is 3

Output:

Connecting to localhost:5432
Database: myapp
True
Too many attempts! Maximum is 3

Важно: переменные уровня модуля разделяются всеми импортами. Если вы измените переменную модуля, это изменение повлияет на весь код, который использует этот модуль:

python
# file1.py
import config
config.DEBUG_MODE = True
print(f"File1 - Debug mode: {config.DEBUG_MODE}")
 
# file2.py
import config
print(f"File2 - Debug mode: {config.DEBUG_MODE}")  # Будет True!

Это поведение может быть полезным, но также и неожиданным. Будьте осторожны, изменяя переменные уровня модуля.

Приватные имена в модулях

По соглашению имена, начинающиеся с подчёркивания, считаются приватными(private) или внутренними(internal) для модуля:

python
# user_manager.py
"""Module for managing user accounts."""
 
# Private helper function
def _validate_email(email):
    """Internal function to validate email format."""
    return '@' in email and '.' in email
 
# Public function
def create_user(username, email):
    """Create a new user account."""
    if not _validate_email(email):
        return None
    
    user = {
        'username': username,
        'email': email,
        'active': True
    }
    return user
 
# Private constant
_MAX_USERNAME_LENGTH = 20
 
# Public constant
MIN_PASSWORD_LENGTH = 8

Когда вы используете from user_manager import *, приватные имена (те, что начинаются с подчёркивания) не импортируются. Однако при необходимости вы всё равно можете обратиться к ним явно:

python
import user_manager
 
# Публичная функция — предназначена для использования
user = user_manager.create_user("alice", "alice@example.com")
 
# Приватная функция — доступна, но не стоит на неё полагаться
# (она может измениться в будущих версиях)
is_valid = user_manager._validate_email("test@test.com")

Префикс подчёркивания — сигнал другим программистам: «Это деталь реализации. Не рассчитывайте, что она останется прежней».

22.5) Понимание пакетов и __init__.py

По мере роста проектов вам захочется организовать несколько связанных модулей в пакет(package). Пакет — это директория, содержащая модули Python и специальный файл __init__.py.

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

Пакет(package) — это способ организовать несколько модуля в иерархическую структуру. Думайте о нём как о папке, содержащей файлы Python, причём саму папку можно импортировать.

Вот простая структура пакета:

myproject/
    main.py
    utilities/
        __init__.py
        text.py
        math.py
        file.py

В этой структуре utilities — пакет, содержащий три модуля: text, math и file. Файл __init__.py (он может быть пустым) сообщает Python, что utilities является пакетом.

Создание простого пакета

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

data_tools/
    __init__.py
    validators.py
    formatters.py

Создайте validators.py:

python
# data_tools/validators.py
"""Data validation functions."""
 
def is_valid_email(email):
    """Check if email has basic valid format."""
    return '@' in email and '.' in email.split('@')[1]
 
def is_valid_phone(phone):
    """Check if phone number has valid format (simple check)."""
    digits = ''.join(c for c in phone if c.isdigit())
    return len(digits) == 10
 
def is_positive_number(value):
    """Check if value is a positive number."""
    try:
        return float(value) > 0
    except (ValueError, TypeError):
        return False

Создайте formatters.py:

python
# data_tools/formatters.py
"""Data formatting functions."""
 
def format_phone(phone):
    """Format phone number as (XXX) XXX-XXXX."""
    digits = ''.join(c for c in phone if c.isdigit())
    if len(digits) != 10:
        return phone
    return f"({digits[:3]}) {digits[3:6]}-{digits[6:]}"
 
def format_currency(amount):
    """Format number as currency."""
    return f"${amount:,.2f}"
 
def format_percentage(value, decimals=1):
    """Format number as percentage."""
    return f"{value * 100:.{decimals}f}%"

Создайте пустой __init__.py:

python
# data_tools/__init__.py
"""Data processing tools package."""

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

Теперь вы можете импортировать из пакета несколькими способами:

python
# Способ 1: Импортировать модуль из пакета
import data_tools.validators
 
email = "user@example.com"
is_valid = data_tools.validators.is_valid_email(email)
print(f"Email valid: {is_valid}")  # Output: Email valid: True
 
# Способ 2: Импортировать конкретный модуль через from
from data_tools import formatters
 
phone = "1234567890"
formatted = formatters.format_phone(phone)
print(f"Formatted phone: {formatted}")  # Output: Formatted phone: (123) 456-7890
 
# Способ 3: Импортировать конкретные функции
from data_tools.validators import is_valid_phone
from data_tools.formatters import format_currency
 
print(is_valid_phone("555-1234"))  # Output: False (not 10 digits)
print(format_currency(1234.56))    # Output: $1,234.56

Файл __init__.py

Файл __init__.py служит двум целям:

  1. Помечает директорию как пакет: Python распознаёт директории с __init__.py как пакеты
  2. Код инициализации пакета: код в __init__.py выполняется при первом импорте пакета

Файл __init__.py может быть пустым, но также может содержать код, упрощающий использование пакета:

python
# data_tools/__init__.py
"""Data processing tools package."""
 
# Импортировать часто используемые функции в пространство имён пакета
from data_tools.validators import is_valid_email, is_valid_phone
from data_tools.formatters import format_phone, format_currency
 
# Версия пакета
__version__ = '1.0.0'
 
# Константа уровня пакета
DEFAULT_CURRENCY_SYMBOL = '$'

Теперь пользователи могут импортировать прямо из пакета:

python
# Вместо: from data_tools.validators import is_valid_email
# Можно написать:
from data_tools import is_valid_email, format_currency
 
print(is_valid_email("test@test.com"))  # Output: True
print(format_currency(99.99))           # Output: $99.99

22.6) Переменная __name__ и if __name__ == "__main__": — запуск файла как скрипта

Файлы Python могут выполнять две роли: их можно импортировать как модули или запускать как автономные скрипты. Специальная переменная __name__ помогает писать код, который хорошо работает в обеих ситуациях.

Понимание __name__

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

  • При импорте: __name__ устанавливается в имя модуля
  • При прямом запуске: __name__ устанавливается в "__main__"

Давайте посмотрим, как это работает. Создайте файл с именем demo_name.py:

python
# demo_name.py
print(f"The __name__ variable is: {__name__}")

Теперь запустите его напрямую:

bash
python demo_name.py

Output:

The __name__ variable is: __main__

Теперь импортируйте его из другого файла:

python
# test_import.py
import demo_name

Output:

The __name__ variable is: demo_name

Когда вы запускаете demo_name.py напрямую, Python устанавливает __name__ в "__main__". Когда вы импортируете его, Python устанавливает __name__ в имя модуля ("demo_name").

Шаблон if __name__ == "__main__":

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

python
if __name__ == "__main__":
    # Код здесь выполняется только когда файл запускают напрямую
    pass

Вот почему это полезно. Создайте math_utils.py:

python
# math_utils.py
"""Utility functions for mathematical operations."""
 
def calculate_area(radius):
    """Calculate the area of a circle."""
    return 3.14159 * radius ** 2
 
def calculate_circumference(radius):
    """Calculate the circumference of a circle."""
    return 2 * 3.14159 * radius
 
# Тестовый код — выполняется только при прямом запуске файла
if __name__ == "__main__":
    print("Testing math_utils functions...")
    
    test_radius = 5
    area = calculate_area(test_radius)
    circumference = calculate_circumference(test_radius)
    
    print(f"Radius: {test_radius}")
    print(f"Area: {area:.2f}")
    print(f"Circumference: {circumference:.2f}")

Когда вы запускаете этот файл напрямую:

bash
python math_utils.py

Output:

Testing math_utils functions...
Radius: 5
Area: 78.54
Circumference: 31.42

Но когда вы импортируете его:

python
# use_math_utils.py
import math_utils
# Тестовый код не выполняется!
 
area = math_utils.calculate_area(10)
print(f"Area of circle: {area:.2f}")  # Output: Area of circle: 314.16

Тестовый код внутри блока if __name__ == "__main__": не выполняется во время импорта. Это позволяет включать тестовый код, примеры или демонстрации в ваши модули, не влияя на код, который их импортирует.

Распространённые применения if __name__ == "__main__":

Тестирование и демонстрации

Включайте примеры, показывающие, как использовать ваш модуль:

python
# string_tools.py
def reverse_string(text):
    """Reverse a string."""
    return text[::-1]
 
def count_vowels(text):
    """Count vowels in text."""
    vowels = 'aeiouAEIOU'
    return sum(1 for char in text if char in vowels)
 
if __name__ == "__main__":
    # Демонстрационный код
    sample = "Hello, World!"
    
    print(f"Original: {sample}")
    print(f"Reversed: {reverse_string(sample)}")
    print(f"Vowels: {count_vowels(sample)}")

В этой главе мы узнали, как организовывать код Python с помощью модулей и пакетов. Мы рассмотрели, как работает система импорта, разные способы импортировать код, а также как создавать собственные модули и пакеты. Мы также узнали о переменной __name__ и шаблоне if __name__ == "__main__":, который позволяет файлам работать и как импортируемым модулям, и как автономным скриптам.

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


© 2025. Primesoft Co., Ltd.
support@primesoft.ai