Модули в языке Python. Разбиение программ на модули. Установка сторонних библиотек

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

Установка модуля

Как правило, вам потребуется устанавливать модули с помощью команды pip install <имя модуля>. Однако, установка некоторых модулей может не потребоваться, так как они включены в стандартную библиотеку Python.

Импорт

Для импорта библиотек используются ключевые слова from и import. Например, давайте импортируем math и напечатаем число пи:

import math

print(math.pi) # 3.141592653589793

Также можно сделать по-другому:

from math import pi

print(pi)

В первом примере мы импортировали весь модуль math, а во втором только pi.

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

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

Для этого существует оператор as, который присваивает новое имя:

from math import pi as p

Также стоит уделить внимание тому, что различные функции могут находиться на разном уровне вложенности:

from sqlalchemy.ext.declarative import declarative_base

В этом случае в модуле sqlalchemy находится папка ext, в которой находится файл declarative.py, в котором находится функция eclarative_base

Импорт собственных модулей

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

├── example2.py
└── example.py

И создадим в файле example2.py функцию hello:

def hello(name):
    print(f"Hello, {name}")

Чтобы использовать эту функцию в example.py нужно ее импортировать. Сделать это можно несколькими способами, как описывалось ранее:

# 1 способ
import example2

example2.hello("Someone")

# 2 способ
from example2 import hello

hello("Someone")

можно также присвоить этим модулям другое имя с помощью as

Каталог как модуль

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

├── example.py
└── folder
    ├── example2.py
    └── \__init__.py

Содержание файла example2.py остается тем же. Теперь давайте воспользуемся функцией hello:

import folder.example2

folder.example2.hello("Someone")

# 2 способ
from folder.example2 import hello

hello("Someone")

# 3 способ
from folder import example2

example2.hello("Someone")

# 4 способ
import folder

folder.example2.hello("Someone")

Все работает как ожидалось.

О файле __init__.py

Этот файл служит для того, чтобы управлять тем, что будет импортироваться при импорте каталога. Например, так выглядит файл utils/__init__.py:

from utils.async_timed import async_timed
from utils.delay import delay
from utils.fetch_status import fetch_status

при такой структуре:

.
├── main.py
└── utils
    ├── __init__.py
    ├── async_timed.py
    ├── delay.py
    └── fetch_status.py

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

from utils import async_timed, delay, fetch_status

async_timed.async_timed()
delay.delay()
fetch_status.fetch_status()

Или более удобный вариант:

from utils.async_timed import async_timed 
from utils.delay import delay 
from utils.fetch_status import fetch_status

async_timed()
delay()
fetch_status()

Но это все не так удобно как вариант ниже:

from utils import async_timed, delay, fetch_status

async_timed()
delay()
fetch_status()

И это все благодаря файлу __init__.py. Все, что мы сделали, это перенесли эти некрасивые многословные импорты в этот файл и теперь импорт из utils стал намного проще. Когда нужно выполнить импорт всего один раз, преимущества небольшие, но представьте, насколько был бы удобен импорт, когда нам нужно пользоваться utils в десятках файлов.

Гибкость благодаря __init__.py

Этот файл позволяет менять внутреннюю структуру каталогов и файлов, не переделывая импорты по всей программе в десятках файлов, а просто изменить импорты в файле __init__.py. Например, я захотел сделать вот так:

.
├── main.py
└── utils
    ├── __init__.py
    ├── a
    │   └── b
    │       └── c
    │           └── delay.py
    ├── async_timed.py
    └── fetch_status.py

Если бы не файл __init__.py, то нам бы пришлось менять from utils.delay import delay на from utils.a.b.c.delay import delay. Ладно в одном файле, а если в нескольких. Но благодаря __init__.py можно изменить только его вот так:

from utils.async_timed import async_timed
from utils.a.b.c.delay import delay
from utils.fetch_status import fetch_status

А все импорты utils в других файлах остались прежними. Удобно!

import * и файл __init__.py

Файл __init__.py может быть как и пустым, так и содержать в себе некоторую информацию. Например, он может содержать информацию о том, какие модули импортируются при использовании import *.

В файле __init__.py можно определить список __all__, который определяет импортируемые модули при использовании import * Давайте это проверим. Для этого создадим каталог и несколько файлов, чтобы получилось вот так:

.
├── example.py
└── folder
    ├── a.py
    ├── b.py
    ├── c.py
    └── \__init__.py

__init__.py:

__all__ = ['a', 'b']

a.py:

def a_function():
    print("a")

b.py:

def b_function():
    print("b")

c.py:

def c_function():
    print("c")

example.py:

from folder import *

a.a_function()
b.b_function()
c.c_function()

При выполнении example.py вывод такой:

a
b
Traceback (most recent call last):
  File "/.../example.py", line 5, in <module>
    c.c_function()
    ^
NameError: name 'c' is not defined

Как видите, модуль c не импортировался так как я не включил его в __all__ в файле __init__.py

__init__.py - самый обычный Python файл

Под этим я имею ввиду то, что вы можете помещать в него абсолютно любой код Python и он будет работать как в самом обычном файле. Допустим, мы добавили в __init__.py новую функцию say_hello(). Обратиться к ней можно будет по имени каталога, в котором находится __init__.py. Если взять примеры выше, то это будет utils, а получить из него say_hello() можно так: from utils import say hello. И так с любыми объектами в __init__.py.

Импорт модулей при сложной структуре каталогов и файлов.

Допустим, у нас есть такая структура:

├── folders
│   ├── folder
│   │   ├── a.py
│   │   └── __init__.py
│   └── folder2
│       ├── example.py
│       └── __init__.py
└── main.py

Как и файла a.py обратиться к example? Вот так:

from ..folder2 import example

теперь в example.py добавим функцию hello:

def hello(name):
    print(f"Hello, {name}")

а в файле a.py вызовем эту функцию:

def hello_world():
    example.hello("World")

Затем в файле main.py импортируем функцию hello_world:

from folders.folder.a import hello_world

hello_world()

Запустив все это командой python main.py, мы получим следующее:

Hello, World

Таким образом, у нас получилась цепочка импорта: folders.folder2.example -> folders.folder.a -> main.

Проблема кругового импорта

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

Для примера, давайте создадим такую структуру файлов

.
├── file.py
└── main.py

Теперь в файле file.py напишем следующее:

from main import name

def hello():
    print(f'hello, {name}')

А в main.py это:

from file import hello

name = "John"

hello()

Запустив этот код, мы получаем ошибку

Traceback (most recent call last):
  File "...\main.py", line 1, in <module>
    from file import hello
  File "...\file.py", line 1, in <module>
    from main import name
  File "...\main.py", line 1, in <module>
    from file import hello
ImportError: cannot import name 'hello' from partially initialized module 'file' (most likely due to a circular import) (...\file.py)

Ошибка говорит о том что нельзя импортировать hello из частично инициализированного модуля file. Это как раз таки круговой импорт.

Когда Python выполняет инструкцию import, он выполняет следующие шаги: 1. Загрузка модуля: Python ищет модуль в кэше (в sys.modules). Если модуль не найден, он загружает его из файла. 2. Инициализация модуля: При загрузке модуля интерпретатор выполняет его код, создавая пространство имен для этого модуля. На этом этапе все функции и классы становятся доступными, но не все из них могут быть выполнены, пока не будет завершена инициализация. 3. Обработка зависимостей: Если модуль A импортирует модуль B, Python начинает загружать B. Если B также пытается импортировать A, возникает круговая зависимость.

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

Резюме

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


ООП. Введение в объектно-ориентированное программирование в Python

Объектно-ориентированное программирование - одна из самых распространенных парадигм. Знание ООП позволит сделать код более гибким и масштабируемым

Pattern matching в Python - конструкция match case

В Python 3.10 был добавлен новый функционал - сопоставление с шаблонами с помощью ключевых слов match и case. В статье также рассматривается производительность match/case по сравнению с if/else.

Классы исключений. Создание собственных исключений

В этой статье описано, что означают различные классы исключений. Также рассмотрено создание собственных классов исключений.

Обработка исключений. Конструкция try/except/finally

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