Как правильно создать свой модуль на Python

В данной статье рассматривается общепринятая структура модуля Python, которой стоит придерживаться при разработке своих модулей

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

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

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

Вся основная логика модуля заключена в отдельные функции или классы. На глобальном уровне могут быть объявлены только константы или необходимые для инициализации модуля операции.

1MOD_CONST = 15 2print(MOD_CONST) 3 4def get_sum(x, y): 5 return (x + y) * MOD_CONST 6 7a, b = 1, 2 8print(get_sum(a, b))

Выше представлен пример плохой структуры модуля. В нем содержаться лишние функции print, а также объявлены не константные переменные a и b, которые проверяют работу функции get_sum.

Ниже представлено более правильное исполнение данного модуля. Если планируется запускать модуль как самостоятельный скрипт, то должны быть определена точка входа в модуль в нее записаны инструкции. В python такой конструкцией является if __name__ == '__main__':

1MOD_CONST = 15 2 3def get_sum(x, y): 4 return (x + y) * MOD_CONST 5 6if __name__ == '__main__': 7 print(MOD_CONST) 8 a, b = 1, 2 9 print(get_sum(a, b))

Как правило, в блок if __name__ == '__main__' заносят все вызовы функций и вывод информации на стандартный поток вывода. Ещё один вариант — создать отдельную main() функцию, переписав в неё всю логику при запуске, и вызывать её в данном блоке. Как это работает? Ваш скрипт может выполняться и самостоятельно, а может быть импортирован как модуль другим скриптом. Чтобы выделить код, который не должен выполняться при импорте его следует поместить в условный оператор с условием if __name__ == '__main__':

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

Структура модуля

Хорошая структура модуля выглядит следующим образом:

  1. Docstring (описание) модуля. В тройных кавычках описывается назначение модуля и его основные команды.
  2. Область импорта различных библиотек. Причем согласно PEP8:
    • Каждый импорт должен быть на отдельной строке. 
    • Импорты должны быть сгруппированы в следующем порядке:
      • импорты из стандартной библиотеки
      • импорты сторонних библиотек
      • импорты модулей текущего проекта
    • При этом вставляйте пустую строку между каждой группой импортов.
  3. Область объявление глобальных констант.
  4. Инициализация модуля.
  5. Область определения функций и классов
  6. if __name__ == '__main__' (метод main) при необходимости.

В качестве примера использования Docstring модуля, представлен пример ниже. Он применим и для многих стандартных функций.Чтобы вывести значение описания после импорта используется стандартная функция help().

1import os 2 3help(os)

В результате получим:

1Help on module os: 2NAME os - OS routines for NT or Posix depending on what system we're on. 3 4DESCRIPTION This exports: 5- all functions from posix or nt, e.g. unlink, stat, etc. 6- os.path is either posixpath or ntpat 7- os.name is either 'posix' or 'nt' 8- os.curdir is a string representing the current directory (always '.') 9- os.pardir is a string representing the parent directory (always '..') 10- os.sep is the (or a most common) pathname separator ('/' or '\\') 11- os.extsep is the extension separator (always '.') 12- os.altsep is the alternate pathname separator (None or '/') 13- os.pathsep is the component separator used in $PATH etc 14- os.linesep is the line separator in text files ('\r' or '\n' or '\r\n') 15- os.defpath is the default search path for executables 16- os.devnull is the file path of the null device ('/dev/null', etc.) Programs that import and use 'os' stand a better chance of being portable between different platforms…

Ниже представлен пример модуля с подобной структурой.

1"""Описание нового модуля, вызываемое с помощью стандартной функции help().""" 2 3import os 4import math 5import pandas as pd 6import numpy as np 7import my_module 8 9MOD_CONST = 15 10 11def main(): 12 print(MOD_CONST) 13 some_func() 14 15def some_func(): 16 print(os.path.abspath()) 17 df = pd.read_csv('test.csv') 18 print(df.head()) 19 20if __name__ == '__main__': 21 main()

В начале модуля представлено описание того, что он выполняет, так называемый docstring. Затем происходит импорт необходимых библиотек. Далее идет объявление констант.

Следом идет инициализация модуля, т.е. функция содержащая код, который будет выполняться при непосредственном запуске модуля. Делается это из-за того, что модуль может содержать большое количество вспомогательных функций. И чтобы не искать основную логику работы модуля по многочисленным строкам кода или прокрутки файла до блока if __name__ == '__main__':, который находиться в самом конце, её выносят в отдельную функцию и ставят на первое место.

Далее идет область определения функций и классов, которые необходимы для работы с модулями. И в конце блок if __name__ == '__main__':, содержащий код, который не должен выполняться при импорте, а только при непосредственном запуске. Заканчиваться модуль всегда пустой строкой.

Тэги:
python
Дата публикации:
21.09.2023

avatar
master
Admin

Похожие статьи