Управление символами и объединение подмодулей

PythonPythonBeginner
Практиковаться сейчас

This tutorial is from open-source community. Access the source code

💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал

Введение

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

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


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python(("Python")) -.-> python/ModulesandPackagesGroup(["Modules and Packages"]) python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python/FunctionsGroup -.-> python/function_definition("Function Definition") python/FunctionsGroup -.-> python/scope("Scope") python/ModulesandPackagesGroup -.-> python/importing_modules("Importing Modules") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") subgraph Lab Skills python/function_definition -.-> lab-132530{{"Управление символами и объединение подмодулей"}} python/scope -.-> lab-132530{{"Управление символами и объединение подмодулей"}} python/importing_modules -.-> lab-132530{{"Управление символами и объединение подмодулей"}} python/classes_objects -.-> lab-132530{{"Управление символами и объединение подмодулей"}} end

Понимание сложности импорта пакетов

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

Текущая структура импорта

Сначала откройте терминал. Терминал - это мощный инструмент, который позволяет взаимодействовать с операционной системой компьютера. После открытия терминала нам нужно перейти в директорию проекта. Директория проекта - это место, где хранятся все файлы, связанные с нашим проектом на Python. Для этого мы будем использовать команду cd, которая означает "change directory" (изменить директорию).

cd ~/project

Теперь, когда мы находимся в директории проекта, давайте рассмотрим текущую структуру пакета structly. Пакет в Python - это способ организации связанных модулей. Мы можем использовать команду ls -la для вывода списка всех файлов и директорий в пакете structly, включая скрытые файлы.

ls -la structly

Вы заметите, что внутри пакета structly есть несколько модулей Python. Эти модули содержат функции и классы, которые мы можем использовать в нашем коде. Однако, если мы хотим использовать функциональность из этих модулей, в настоящее время нам нужно использовать длинные инструкции импорта. Например:

from structly.structure import Structure
from structly.reader import read_csv_as_instances
from structly.tableformat import create_formatter, print_table

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

Давайте начнем с рассмотрения содержимого файла __init__.py пакета. Файл __init__.py - это специальный файл в пакетах Python. Он выполняется при импорте пакета и может использоваться для инициализации пакета и настройки необходимых импортов.

cat structly/__init__.py

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

Цель

К концу этого практического занятия (лабораторной работы) наша цель - иметь возможность использовать гораздо более простые инструкции импорта. Вместо длинных путей импорта, которые мы видели ранее, мы сможем использовать инструкции, такие как:

from structly import Structure, read_csv_as_instances, create_formatter, print_table

Или даже:

from structly import *

Использование этих более простых инструкций импорта сделает наш код более чистым и легким для работы. Это также сэкономит наше время и усилия при написании и поддержке кода.

Управление экспортируемыми символами с помощью __all__

В Python, когда вы используете инструкцию from module import *, вы, возможно, захотите контролировать, какие символы (функции, классы, переменные) импортируются из модуля. Именно здесь пригодится переменная __all__. Инструкция from module import * представляет собой способ импорта всех символов из модуля в текущее пространство имен. Однако иногда вы не хотите импортировать каждый отдельный символ, особенно если их много или если некоторые из них предназначены только для внутреннего использования в модуле. Переменная __all__ позволяет вам точно указать, какие символы должны быть импортированы при использовании этой инструкции.

Что такое __all__?

Переменная __all__ представляет собой список строк. Каждая строка в этом списке представляет символ (функцию, класс или переменную), который экспортирует модуль, когда кто - то использует инструкцию from module import *. Если переменная __all__ не определена в модуле, инструкция import * импортирует все символы, которые не начинаются с подчеркивания. Символы, начинающиеся с подчеркивания, обычно считаются приватными или внутренними для модуля и не предназначены для прямого импорта.

Изменение каждого подмодуля

Теперь давайте добавим переменную __all__ в каждый подмодуль пакета structly. Это поможет нам контролировать, какие символы экспортируются из каждого подмодуля, когда кто - то использует инструкцию from module import *.

  1. Сначала изменим файл structure.py:
touch ~/project/structly/structure.py

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

__all__ = ['Structure']

Эта строка сообщает Python, что когда кто - то использует from structure import *, будет импортирован только символ Structure. Сохраните файл и выйдите из редактора.

  1. Далее изменим файл reader.py:
touch ~/project/structly/reader.py

Эта команда создает новый файл с именем reader.py в директории structly. Теперь просмотрите файл, чтобы найти все функции, которые начинаются с read_csv_as_. Эти функции - те, которые мы хотим экспортировать. Затем добавьте список __all__ со всеми именами этих функций. Он должен выглядеть примерно так:

__all__ = ['read_csv_as_instances', 'read_csv_as_dicts', 'read_csv_as_columns']

Обратите внимание, что фактические имена функций могут отличаться в зависимости от того, что вы найдете в файле. Убедитесь, что вы добавили все функции read_csv_as_*, которые нашли. Сохраните файл и выйдите из редактора.

  1. Теперь изменим файл tableformat.py:
touch ~/project/structly/tableformat.py

Эта команда создает новый файл с именем tableformat.py в директории structly. Добавьте следующую строку в верхней части файла:

__all__ = ['create_formatter', 'print_table']

Эта строка указывает, что когда кто - то использует from tableformat import *, будут импортированы только символы create_formatter и print_table. Сохраните файл и выйдите из редактора.

Единый импорт в __init__.py

Теперь, когда каждый модуль определяет, что он экспортирует, мы можем обновить файл __init__.py, чтобы импортировать все эти символы. Файл __init__.py - это специальный файл в пакетах Python. Он выполняется при импорте пакета и может использоваться для инициализации пакета и импорта символов из подмодулей.

touch ~/project/structly/__init__.py

Эта команда создает новый файл __init__.py в директории structly. Измените содержимое файла на следующее:

## structly/__init__.py

from .structure import *
from .reader import *
from .tableformat import *

Эти строки импортируют все экспортируемые символы из подмодулей structure, reader и tableformat. Точка (.) перед именами модулей указывает, что это относительные импорты, то есть импорты из того же пакета. Сохраните файл и выйдите из редактора.

Тестирование наших изменений

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

touch ~/project/test_structly.py

Эта команда создает новый файл с именем test_structly.py в директории проекта. Добавьте следующее содержимое в файл:

## A simple test to verify our imports work correctly

from structly import Structure
from structly import read_csv_as_instances
from structly import create_formatter, print_table

print("Successfully imported all required symbols!")

Эти строки пытаются импортировать класс Structure, функцию read_csv_as_instances и функции create_formatter и print_table из пакета structly. Если импорт прошел успешно, программа выведет сообщение "Successfully imported all required symbols!". Сохраните файл и выйдите из редактора. Теперь давайте запустим этот тест:

cd ~/project
python test_structly.py

Команда cd ~/project изменяет текущую рабочую директорию на директорию проекта. Команда python test_structly.py запускает скрипт test_structly.py. Если все работает правильно, вы должны увидеть на экране сообщение "Successfully imported all required symbols!".

✨ Проверить решение и практиковаться

Экспорт всего из пакета

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

Добавление __all__ в пакет

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

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

touch ~/project/structly/__init__.py

Теперь откройте файл __init__.py и добавьте список __all__. Этот список должен включать все символы, которые мы хотим экспортировать. Символы сгруппированы в зависимости от того, откуда они приходят, например, из модулей structure, reader и tableformat.

## structly/__init__.py

from .structure import *
from .reader import *
from .tableformat import *

## Define what symbols are exported when using "from structly import *"
__all__ = ['Structure',  ## from structure
           'read_csv_as_instances', 'read_csv_as_dicts', 'read_csv_as_columns',  ## from reader
           'create_formatter', 'print_table']  ## from tableformat

После добавления кода сохраните файл и выйдите из редактора.

Понимание import *

Шаблон from module import * в целом не рекомендуется для использования в большинстве Python - кода. Есть несколько причин для этого:

  1. Он может загрязнить ваше пространство имен неожиданными символами. Это означает, что в текущем пространстве имен могут появиться переменные или функции, которые вы не ожидали, что может привести к конфликтам имен.
  2. Он делает неясным, откуда именно приходят определенные символы. Когда вы используете import *, сложно понять, из какого модуля пришел тот или иной символ, что может усложнить понимание и поддержку кода.
  3. Он может привести к проблемам с переопределением (shadowing). Переопределение возникает, когда локальная переменная или функция имеет то же имя, что и переменная или функция из другого модуля, что может привести к неожиданному поведению.

Однако есть определенные случаи, когда использование import * уместно:

  • Для пакетов, которые предназначены для использования как единое целое. Если пакет должен использоваться как единый блок, то использование import * может упростить доступ ко всем необходимым символам.
  • Когда пакет определяет четкий интерфейс с помощью __all__. Используя список __all__, вы можете контролировать, какие символы экспортируются, что делает использование import * более безопасным.
  • Для интерактивного использования, например, в Python REPL (Read - Eval - Print Loop). В интерактивной среде может быть удобно импортировать все символы сразу.

Тестирование с использованием import *

Чтобы убедиться, что мы можем импортировать все символы сразу, давайте создадим еще один тестовый файл. Мы используем команду touch для создания файла.

touch ~/project/test_import_all.py

Теперь откройте файл test_import_all.py и добавьте следующее содержимое. Этот код импортирует все символы из пакета structly и затем проверяет, доступны ли некоторые важные символы.

## Test importing everything at once

from structly import *

## Try using the imported symbols
print(f"Structure symbol is available: {Structure is not None}")
print(f"read_csv_as_instances symbol is available: {read_csv_as_instances is not None}")
print(f"create_formatter symbol is available: {create_formatter is not None}")
print(f"print_table symbol is available: {print_table is not None}")

print("All symbols successfully imported!")

Сохраните файл и выйдите из редактора. Теперь давайте запустим тест. Сначала перейдите в директорию проекта с помощью команды cd, а затем запустите Python - скрипт.

cd ~/project
python test_import_all.py

Если все настроено правильно, вы должны увидеть подтверждение, что все символы были успешно импортированы.

✨ Проверить решение и практиковаться

Разделение модуля для лучшей организации кода

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

Понимание текущей структуры

Модуль tableformat.py представляет собой хороший пример большого модуля. Он содержит несколько классов - форматеров, каждый из которых отвечает за форматирование данных различным образом:

  • TableFormatter (базовый класс): Это базовый класс для всех остальных классов - форматеров. Он определяет базовую структуру и методы, которые будут унаследованы и реализованы другими классами.
  • TextTableFormatter: Этот класс форматирует данные в простом текстовом формате.
  • CSVTableFormatter: Этот класс форматирует данные в формате CSV (Comma - Separated Values, значения, разделенные запятыми).
  • HTMLTableFormatter: Этот класс форматирует данные в формате HTML (Hypertext Markup Language, язык гипертекстовой разметки).

Мы переорганизуем этот модуль в структуру пакета с отдельными файлами для каждого типа форматера. Это сделает код более модульным и легче управляемым.

Шаг 1: Очистка кэш - файлов

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

cd ~/project/structly
rm -rf __pycache__

В приведенных выше командах cd ~/project/structly изменяет текущую директорию на директорию structly в вашем проекте. rm -rf __pycache__ удаляет директорию __pycache__ и все ее содержимое. Опция -r означает рекурсивное удаление, то есть будут удалены все файлы и поддиректории внутри директории __pycache__. Опция -f означает принудительное удаление, то есть файлы будут удалены без запроса подтверждения.

Шаг 2: Создание новой структуры пакета

Теперь давайте создадим новую структуру директорий для нашего пакета. Мы создадим директорию с именем tableformat и поддиректорию с именем formats внутри нее.

mkdir -p tableformat/formats

Команда mkdir используется для создания директорий. Опция -p означает создание всех необходимых родительских директорий, если они не существуют. Таким образом, если директория tableformat не существует, она будет создана сначала, а затем внутри нее будет создана директория formats.

Шаг 3: Перемещение и переименование исходного файла

Далее мы переместим исходный файл tableformat.py в новую структуру и переименуем его в formatter.py.

mv tableformat.py tableformat/formatter.py

Команда mv используется для перемещения или переименования файлов. В данном случае мы перемещаем файл tableformat.py в директорию tableformat и переименовываем его в formatter.py.

Шаг 4: Разделение кода на отдельные файлы

Теперь нам нужно создать файлы для каждого форматера и переместить в них соответствующий код.

1. Создание файла базового форматера

touch tableformat/formatter.py

Команда touch используется для создания пустого файла. В данном случае мы создаем файл с именем formatter.py в директории tableformat.

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

## Base TableFormatter class and utility functions

__all__ = ['TableFormatter', 'print_table', 'create_formatter']

class TableFormatter:
    def headings(self, headers):
        '''
        Emit table headings.
        '''
        raise NotImplementedError()

    def row(self, rowdata):
        '''
        Emit a single row of table data.
        '''
        raise NotImplementedError()

def print_table(objects, columns, formatter):
    '''
    Make a nicely formatted table from a list of objects and attribute names.
    '''
    formatter.headings(columns)
    for obj in objects:
        rowdata = [getattr(obj, name) for name in columns]
        formatter.row(rowdata)

def create_formatter(fmt):
    '''
    Create an appropriate formatter given an output format name.
    '''
    if fmt == 'text':
        from .formats.text import TextTableFormatter
        return TextTableFormatter()
    elif fmt == 'csv':
        from .formats.csv import CSVTableFormatter
        return CSVTableFormatter()
    elif fmt == 'html':
        from .formats.html import HTMLTableFormatter
        return HTMLTableFormatter()
    else:
        raise ValueError(f'Unknown format {fmt}')

Переменная __all__ используется для указания, какие символы должны быть импортированы при использовании from module import *. В данном случае мы указываем, что должны быть импортированы только символы TableFormatter, print_table и create_formatter.

Класс TableFormatter является базовым классом для всех остальных классов - форматеров. Он определяет два метода, headings и row, которые должны быть реализованы подклассами.

Функция print_table - это вспомогательная функция, которая принимает список объектов, список имен столбцов и объект - форматер и выводит данные в отформатированной таблице.

Функция create_formatter - это фабричная функция, которая принимает имя формата в качестве аргумента и возвращает соответствующий объект - форматер.

Сохраните файл и выйдите из редактора после внесения этих изменений.

2. Создание текстового форматера

touch tableformat/formats/text.py

Мы добавим только класс TextTableFormatter в этот файл.

## Text formatter implementation

__all__ = ['TextTableFormatter']

from ..formatter import TableFormatter

class TextTableFormatter(TableFormatter):
    '''
    Emit a table in plain-text format
    '''
    def headings(self, headers):
        print(' '.join('%10s' % h for h in headers))
        print(('-'*10 + ' ')*len(headers))

    def row(self, rowdata):
        print(' '.join('%10s' % d for d in rowdata))

Переменная __all__ указывает, что при использовании from module import * должен быть импортирован только символ TextTableFormatter.

Инструкция from ..formatter import TableFormatter импортирует класс TableFormatter из файла formatter.py в родительской директории.

Класс TextTableFormatter наследуется от класса TableFormatter и реализует методы headings и row для форматирования данных в простом текстовом формате.

Сохраните файл и выйдите из редактора после внесения этих изменений.

3. Создание CSV - форматера

touch tableformat/formats/csv.py

Мы добавим только класс CSVTableFormatter в этот файл.

## CSV formatter implementation

__all__ = ['CSVTableFormatter']

from ..formatter import TableFormatter

class CSVTableFormatter(TableFormatter):
    '''
    Output data in CSV format.
    '''
    def headings(self, headers):
        print(','.join(headers))

    def row(self, rowdata):
        print(','.join(str(d) for d in rowdata))

Подобно предыдущим шагам, мы указываем переменную __all__, импортируем класс TableFormatter и реализуем методы headings и row для форматирования данных в формате CSV.

Сохраните файл и выйдите из редактора после внесения этих изменений.

4. Создание HTML - форматера

touch tableformat/formats/html.py

Мы добавим только класс HTMLTableFormatter в этот файл.

## HTML formatter implementation

__all__ = ['HTMLTableFormatter']

from ..formatter import TableFormatter

class HTMLTableFormatter(TableFormatter):
    '''
    Output data in HTML format.
    '''
    def headings(self, headers):
        print('<tr>', end='')
        for h in headers:
            print(f'<th>{h}</th>', end='')
        print('</tr>')

    def row(self, rowdata):
        print('<tr>', end='')
        for d in rowdata:
            print(f'<td>{d}</td>', end='')
        print('</tr>')

Снова мы указываем переменную __all__, импортируем класс TableFormatter и реализуем методы headings и row для форматирования данных в формате HTML.

Сохраните файл и выйдите из редактора после внесения этих изменений.

Шаг 5: Создание файлов инициализации пакета

В Python файлы __init__.py используются для пометки директорий как Python - пакетов. Мы должны создать файлы __init__.py как в директории tableformat, так и в директории formats.

1. Создание файла для пакета tableformat

touch tableformat/__init__.py

Добавьте следующее содержимое в файл:

## Re-export the original symbols from tableformat.py
from .formatter import *

Эта инструкция импортирует все символы из файла formatter.py и делает их доступными при импорте пакета tableformat.

Сохраните файл и выйдите из редактора после внесения этих изменений.

2. Создание файла для пакета formats

touch tableformat/formats/__init__.py

Вы можете оставить этот файл пустым или добавить простую строки документации (docstring):

'''
Format implementations for different output formats.
'''

Строка документации дает краткое описание того, что делает пакет formats.

Сохраните файл и выйдите из редактора после внесения этих изменений.

Шаг 6: Тестирование новой структуры

Давайте создадим простой тест, чтобы убедиться, что наши изменения работают правильно.

cd ~/project
touch test_tableformat.py

Добавьте следующее содержимое в файл test_tableformat.py:

## Test the tableformat package restructuring

from structly import *

## Create formatters of each type
text_fmt = create_formatter('text')
csv_fmt = create_formatter('csv')
html_fmt = create_formatter('html')

## Define some test data
class TestData:
    def __init__(self, name, value):
        self.name = name
        self.value = value

## Create a list of test objects
data = [
    TestData('apple', 10),
    TestData('banana', 20),
    TestData('cherry', 30)
]

## Test text formatter
print("\nText Format:")
print_table(data, ['name', 'value'], text_fmt)

## Test CSV formatter
print("\nCSV Format:")
print_table(data, ['name', 'value'], csv_fmt)

## Test HTML formatter
print("\nHTML Format:")
print_table(data, ['name', 'value'], html_fmt)

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

Сохраните файл и выйдите из редактора после внесения этих изменений. Теперь запустите тест:

python test_tableformat.py

Вы должны увидеть одни и те же данные, отформатированные тремя разными способами (текстом, CSV и HTML). Если вы видите ожидаемый вывод, это означает, что переорганизация вашего кода прошла успешно.

✨ Проверить решение и практиковаться

Резюме

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

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