Фабрики в Python
В программировании довольно часто встречается задача: в зависимости от входных данных нужно выбрать подходящий класс для обработки. Например, у нас есть XML-файлы и JSON-файлы, и мы хотим написать систему, которая будет их импортировать.
На первый взгляд решение простое — внутри метода проверять расширение файла и создавать соответствующий объект. Но у такого подхода есть несколько серьёзных недостатков. Именно здесь на помощь приходит паттерн «Фабрика».
Рассмотрим упрощённый пример кода:
На первый взгляд код рабочий, но здесь нарушаются два принципа SOLID:
1️⃣ Принцип единственной ответственности (Single Responsibility Principle): класс Document отвечает сразу за создание объектов XMLImporter, JSONImporter и их использование.
2️⃣ Принцип открытости / закрытости (Open / Closed Principle).
Если появится новый формат (например, CSV), придётся снова лезть в метод import_file и расширять блок if / elif.
Решение: фабрика
Чтобы убрать ответственность за создание объектов из класса Document, вводится так называемася фабрика. Это объект, который берёт на себя ответственность за создание других объектов. Она не избавляет нас полностью от изменения кода при добавлении новых форматов, но значительно улучшает читаемость, гибкость и расширяемость системы.
Теперь
Преимущества фабрики
1️⃣ Изоляция ответственности: логика создания объектов теперь сосредоточена только в одном месте — в ImporterFactory.
2️⃣ Гибкость: если нужно добавить поддержку нового формата (CSV, YAML и т.д.), достаточно изменить фабрику, не трогая Document.
3️⃣ Удобное тестирование: в тестах можно подменить фабрику на «фиктивную» (mock) и проверять работу Document независимо от импортеров.
Что дальше?
Фабрика — это базовый паттерн. На его основе строятся более сложные подходы, например:
🔘 Factory Method — когда создание объектов делегируется подклассам.
🔘 Abstract Factory — когда фабрика создаёт целые семейства объектов, согласованных между собой.
#основы
@zen_of_python
В программировании довольно часто встречается задача: в зависимости от входных данных нужно выбрать подходящий класс для обработки. Например, у нас есть XML-файлы и JSON-файлы, и мы хотим написать систему, которая будет их импортировать.
На первый взгляд решение простое — внутри метода проверять расширение файла и создавать соответствующий объект. Но у такого подхода есть несколько серьёзных недостатков. Именно здесь на помощь приходит паттерн «Фабрика».
Рассмотрим упрощённый пример кода:
class XMLImporter:
def __init__(self, filename):
self.filename = filename
def execute(self):
print("XML imported from", self.filename)
class JSONImporter:
def __init__(self, filename):
self.filename = filename
def execute(self):
print("JSON imported from", self.filename)
class Document:
def import_file(self, filename):
if filename.endswith(".xml"):
importer = XMLImporter(filename)
elif filename.endswith(".json"):
importer = JSONImporter(filename)
data = importer.execute()
document = Document()
document.import_file("text.xml")
document.import_file("text.json")
На первый взгляд код рабочий, но здесь нарушаются два принципа SOLID:
Если появится новый формат (например, CSV), придётся снова лезть в метод import_file и расширять блок if / elif.
Решение: фабрика
Чтобы убрать ответственность за создание объектов из класса Document, вводится так называемася фабрика. Это объект, который берёт на себя ответственность за создание других объектов. Она не избавляет нас полностью от изменения кода при добавлении новых форматов, но значительно улучшает читаемость, гибкость и расширяемость системы.
class ImporterFactory:
def get_importer(self, filename):
if filename.endswith(".xml"):
return XMLImporter(filename)
elif filename.endswith(".json"):
return JSONImporter(filename)
Теперь
Document
больше не знает, как именно создаются нужные классы. Ему просто передают готовую фабрику:
class Document:
def __init__(self, factory):
self.factory = factory
def import_file(self, filename):
importer = self.factory.get_importer(filename)
data = importer.execute()
factory = ImporterFactory()
document = Document(factory)
document.import_file("text.xml")
document.import_file("text.json")
Преимущества фабрики
Что дальше?
Фабрика — это базовый паттерн. На его основе строятся более сложные подходы, например:
#основы
@zen_of_python
Please open Telegram to view this post
VIEW IN TELEGRAM
👎4🔥2❤1
Что такое замыкание и зачем оно нужно
Замыкания (Closures) — понятие, которое кажется сложным при первом знакомстве. Но на самом деле вы уже его скорее всего используете неосознанно, настолько это стало базой.
Представим программу, где пользователь вводит число, нажимает OK, и программа сохраняет это число в список, выводя все введённые значения:
Код работает, но есть проблема: переменная
🔘 функция зависит от переменной, объявленной в другом месте;
🔘 код становится менее гибким — нельзя просто перенести функцию в другой модуль, не взяв с собой
Замыкание помогает «связать» данные и логику в одном месте без использования классов:
Когда мы вызываем внешнюю функцию
Ключевая идея замыкания:
Даже когда
Используйте замыкания, если хотите:
🔘 инкапсулировать состояние в функции без создания класса;
🔘 нужно создать функцию-конфигуратор (например, с частично зафиксированными параметрами);
Замыкание — это функция, которая:
🔘 определена внутри другой функции;
🔘 использует переменные из внешней функции;
🔘 «запоминает» эти переменные даже после завершения внешней функции.
#основы
@zen_of_python
Замыкания (Closures) — понятие, которое кажется сложным при первом знакомстве. Но на самом деле вы уже его скорее всего используете неосознанно, настолько это стало базой.
Представим программу, где пользователь вводит число, нажимает OK, и программа сохраняет это число в список, выводя все введённые значения:
numbers = []
def enter_number(x):
numbers.append(x)
print(numbers)
enter_number(3) # [3]
enter_number(7) # [3, 7]
enter_number(4) # v
Код работает, но есть проблема: переменная
numbers
находится вне функции, то есть она глобальная. Это значит, что:numbers
.Замыкание помогает «связать» данные и логику в одном месте без использования классов:
def enter_number_outer():
numbers = [] # локальная переменная
def enter_number_inner(x):
numbers.append(x)
print(numbers)
return enter_number_inner
Когда мы вызываем внешнюю функцию
enter_number_outer()
, она создаёт свой контекст с переменной numbers
и возвращает внутреннюю функцию, которая имеет к ней доступ.
enter_num = enter_number_outer()
enter_num(3) # [3]
enter_num(7) # [3, 7]
enter_num(4) # [3, 7, 4]
Ключевая идея замыкания:
Внутренняя функция «замыкает» (сохраняет) значения переменных из области видимости внешней функции.
Даже когда
enter_number_outer()
завершает выполнение, её переменные не уничтожаются, потому что они нужны внутренней функции, которая всё ещё существует. Это и есть closure — функция, которая запоминает контекст, в котором была создана.Используйте замыкания, если хотите:
Замыкание — это функция, которая:
#основы
@zen_of_python
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12❤2🤣1
Как писать docstrings
Докстринги (буквально «строки документации») — это встроенная в код документация (обычно после инициализации функции / класса и прочих объектов между двумя '''), которую могут читать люди и инструменты (help(), pydoc, автогенераторы). В этом лонгриде мы разберемся, где и как их писать.
Зачем нужны docstrings — и чем они отличаются от комментариев
🔘 Комментарии (#) объясняют реализацию и помогают разработчикам; интерпретатор их игнорирует.
🔘 Докстринги — это строковые литералы (обычно в
Докстринги описывают интерфейс (что делает код, какие аргументы и что возвращает), а комментарий — реализацию и все остальное.
Многострочные докстринги используются когда нужно подробнее описать параметры, поведение, побочные эффекты, примеры использования. По PEP 257 закрывающие кавычки обычно ставят на отдельной строке в многострочном docstring:
Чтобы получить доступ к docstring в коде и терминале, вызываем:
🔘
🔘
🔘
Что писать в docstring для модулей, функций и классов
Модуль:
🔘 Краткое описание назначения модуля.
🔘 При необходимости — описание экспортируемых переменных/классов/функций, примеры использования.
Функция / метод:
🔘 Краткое резюме (1–2 предложения).
🔘 Секция
🔘 Секция
🔘 Исключения: какие ошибки может выбросить функция (опционально, но полезно).
🔘 Пример использования или заметки о поведении (если нужно).
Класс:
🔘 Краткое описание назначения класса.
🔘 Описание атрибутов (публичных), краткая информация о методах (если интерфейс не очевиден).
🔘 Для сложных иерархий — примеры создания/использования. ([realpython.com][1])
#основы
@zen_of_python
Докстринги (буквально «строки документации») — это встроенная в код документация (обычно после инициализации функции / класса и прочих объектов между двумя '''), которую могут читать люди и инструменты (help(), pydoc, автогенераторы). В этом лонгриде мы разберемся, где и как их писать.
Зачем нужны docstrings — и чем они отличаются от комментариев
"""`), помещённые сразу после определения модуля / функции / класса / метода; они сохраняются в атрибуте
.__doc__` и доступны в рантайме (через .__doc__
, help()
и инструментах вроде pydoc
. Докстринги описывают интерфейс (что делает код, какие аргументы и что возвращает), а комментарий — реализацию и все остальное.
Многострочные докстринги используются когда нужно подробнее описать параметры, поведение, побочные эффекты, примеры использования. По PEP 257 закрывающие кавычки обычно ставят на отдельной строке в многострочном docstring:
def get_book(publication_year, title):
"""
Retrieve a Harry Potter book by its publication year and name.
Parameters:
publication_year (int): The year the book was published.
title (str): The title of the book.
Returns:
str: A sentence describing the book and its publication year.
"""
Чтобы получить доступ к docstring в коде и терминале, вызываем:
obj.__doc__
— возвращает сырой docstring (часто краткий);help(obj)
— даёт структурированный вывод, полезный для модулей и классов;python -m pydoc module
— позволяет просматривать документацию из терминала и генерировать статичные страницы. Что писать в docstring для модулей, функций и классов
Модуль:
Функция / метод:
Parameters`/`Args
: имена параметров, типы, краткое описание.Returns
/ Yields
: что возвращается, тип.Класс:
#основы
@zen_of_python
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1👍1