× К оглавлению На главную Об авторе

Дата и время публикации:    
Дата и время модификации:

Назначение и использование

1. Назначение

1.1 Конструкция __main__

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

Оба эти механизма связаны непосредственно с модулями Python и то, как пользователи взаимодействуют с ними и как эти модули взаимодействуют друг с другом [3.1].

1.2 Конструкция __init__

В Python, конструкцию __init__ только в качестве имени файла в отличии от __main__. Это файл __init__.py в основном используется для интерпретации того, что директория, содержащая этот файл является содержимым пакета Python. Это позволяет избежать непреднамеренного пропуска директорий с общими именами, таких как string, которые могут повстречаться на пути поиска. При этом, т.к. файл является обязательным, он должен быть пустым или, как минимум, содержать переменную __all__

Так, c помощью переменной __all__, можно задать перечень объектов и, в том числе, функций, которые будут импортироваться из подключаемого модуля c использованием команды import * и могут быть интерпретированы в качестве публичных [3.3].

Поэтому в версии shellenv-1.1, в этой переменной перечислены основные функции, которые будут доступны публично, как показано в листинге 1.2.1

Листинг 1.2.1

...
all = [ 'unsetenv', 
        'setenv', 
        'getenv', 
        'nmaxhitsenv', 
        'nminhitsenv', 
        'changenv' ] 
...

2. Использование

2.1 Выражение __name__ == '__main__'

Когда модуль или пакет импортируется, __name__ устанавливается по имени модуля. Обычно, таким именем является само имя подключаемого Python-файла без расширения, как показано в дампе 2.1.1

Листинг 2.1.1

>>> from shellenv_module import shellenv
>>> shellenv.__name__
'shellenv_module.shellenv'

Однако, если модуль выполняется на верхней точки кода среды выполнения (top-level code environment), его __name__ будет содержать текстовую строку '__main__'

2.2 Совместное использование __main__.py и __init__.py

Используется, когда модуль был установлен вместе с пакетом в окружение Python запущен с командой, например, python3 -m , тогда файл __main__.py [3.2]. Иначе, будет возвращена ошибка, которая показан в дампе 2.2.1

Дамп 2.2.1

$ python3 -m shellenv
/usr/bin/python3: No module named shellenv.__main__; 'shellenv' is a package and cannot be directly executed

Для того, чтобы можно было запускать пакет shellenv через команду python3 -m, в директории src/shellenv нужно определить относительный импорт для модуля Python, как показано в листинге 2.2.2 и 2.2.3

Листинг 2.2.2 – файл __init__.py

...
from typing import Any, Optional

from .shellenv import ( unsetenv, setenv, getenv, nmaxhitsenv, nminhitsenv, changenv, print_all )
...

Листинг 2.2.3 – файл __main__.py

def run_printall()→bool:
    ... 
    if print_all() == 0 :
       ...   
...
def main(argv=None)->None:
      ...
      if run_printall() == False: 
      ...    
if __name__ == '__main__':
   main()
...   

Как показано в дампе 2.2.2 и 2.2.3, в случае использования кода в качестве модуля Python просто выполнить import shellenv неполучиться, т.к. в этом случае все может закончится с ошибкой: "ModuleNotFoundError: No module named 'shellenv'"

Для разрешения этой ошибки необходимо выполнить относительный импорт точки выполнения, которой является, например в Debian, ${HOME}/.local/lib/python3.x/site-packages/shellenv т.к., например в Debian, пакет shellenv будет установлен pipy в директорию ${HOME}/.local/lib/python3.x/site-packages/shellenv и относительно нее выполняется код модуля по команде: "python3 -m shellenv"

Также не следует выполнять относительный импорт в __main__.py, а только в __init__.py . Иначе при импортировании может возникнуть ошибка, которая показана в дампе 2.2.4

Листинг 2.2.4

>>> from shellenv import print_all
Traceback (most recent call last):
  File "", line 1, in 
ImportError: cannot import name 'print_all' from 'shellenv' (/home/user/.local/lib/python3.9/site-packages/shellenv/__init__.py)

Относительное импортирование производится относительно направляющих точек (leading dots), которые указывают направление относительного импорта от текущего пакета. Две или более направляющих точек осуществляют импорт к родительскому отталкиваясь от текущего пакета, отсчитывая по одной точки на каждый уровень от базового [3.4], как показано в листинге 2.2.5

Листинг 2.2.5 – Пример относительного импорта из PEP 328

"""
Here's sample package layout that may locates in user’s local directory (e.g. ${HOME}/.local/lib/python3.x/site-packages/):   
package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
        moduleY.py
    subpackage2/
        __init__.py
        moduleZ.py
    moduleA.py
Assuming that the current file is either moduleX.py or subpackage1/__init__.py, following are correct usages of the new syntax:
"""
from .moduleY import spam
from .moduleY import spam as ham
from . import moduleY
from ..subpackage1 import moduleY
from ..subpackage2.moduleZ import eggs
from ..moduleA import foo
from ...package import bar
from ...sys import path

Предполагается, что текущим файл, выполняющий относительный импорт является либо moduleX.py или subpackage1/__init__.py . При этом, файл __init__.py может заменить __main__.py, как будет показано далее.

2.3 Импорт между файлами-компонентами модулям

Это понадобится, когда будет расти число компонентов модуля в виде файлов. Как, например, когда перенес часть функций из __main__, осуществляющих тестирование, были перенесены в файл testrun.py и проект shellenv приобрел вид, как показано в дампе 2.3.1

Дамп 2.3.1

.
├── LICENSE
├── pyproject.toml
├── README.md
├── setup.cfg
└── src
   └── shellenv
      ├── __init__.py
      ├── __main__.py
      ├── shellenv.py
      └── testrun.py

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

Листинге 2.3.2

import os
import shellenv

__sherryhw = 'Sherry platform' 
 
def __run_printall()->bool:
...
def __run_scanforhits()->bool:
...
def __run_setenv()->bool:
...
def __run_unsetenv()->bool:
...
def __run_dotenv()→bool:
...   
def functionality_check(argv=None)→None:
    if __run_printall() == False: 
      ... 
    if __run_scanforhits() == False:
      ...
    if __run_setenv() == False:     
      ...
    if __run_unsetenv() == False:
      ... 
    if __run_dotenv() == False: 
      ...

Соответственно, функция __main__.py приобрела вид, как показано в листинге 2.3.3

Листинге 2.3.3

#!/usr/bin/env python3

""" shellenv-run.py: tests Python's module python-shellenv to display enviroment info"""

__author__      = "Andrew Rzhaskov"
__copyright__   = "Copyright 2022, Russia Moscow"

import os
import sys

"""
   This module runs command python3 -m or executes each individual function in 
   command line.
"""
  
if __name__ == '__main__':
       from shellenv import functionality_check 
       sys.exit(functionality_check())

И все-таки именования уровня модуля с двойными и тройными подчеркиванием (Module level "dunders"), такие как __author__ и __copyright__, лучше переместить из кода, связанный с реализацией функционала модуля, как в случае с __main__.py, в код отвечающий за его иницилизацию.

Поэтому в реализации версии python-shellenv-1.5 перенес __author__ , __copyright__ из файла shellenv.py и добавил в __version__ в __init__.py, отвечающий за инициализацию модуля и как показано в листинге 2.3.4

Листинге 2.3.4

...
__author__      = "Andrew Rzhaskov"
__copyright__   = "Copyright 2022, Russia Moscow"
__version__      = "1.5" 
...

 

3. Библиография

3.1 __main__ — Top-level code environment

3.2 What's up with __init__.py and __main__.py ?

3.3 What Is Python __all__ And How To Use It

3.4 PEP 328 – Imports: Multi-Line and Absolute/Relative

Сайт разработан в соответствии с рекомендациями консорциума W3C для языка разметки HTML5.

Об авторе можно прочитать здесь.

Copyright © 2015-2022 Андрей Ржавсков