Долгое время после изучения основ python меня мучало два вопроса:
- ЗАЧЕМ мне вообще нужны классы при написании скриптов?
- КАК мне использовать фишки ООП при написании скриптов?
Решая небольшие задачи и правда очень сложно найти им применение.
Сейчас же я до сих пор не отрицаю, что любую задачу можно решить скриптом-одностраничником не используя всех доступных нам возможностей, однако такие скрипты будет очень сложно сопровождать не только левому человеку, но и Вам. Даже сам процесс написания такого кода может вызывать боль.
И тут уже совершенно не важно, это скрипт «на один раз» или полноценная cli утилита для множественного использования.
В этой статье я не буду скатываться к объяснению, почему нам нужно разбивать код на функции, разносить по файлам и прочее. Это уже как-то без меня.
Я приведу лишь 1 частный пример использования классов, так как именно они у меня вызывали недопонимание — как применять к своей сфере. Возможно, этот пример может послужить толчком для таких же как и я.
Кейс
У вас есть много проектов, которые вы сопровождаете. Для каждого проекта у вас есть часть yaml файлов в одном репозитории и часть в другом.
Вам нужно проанализировать содержимое из первого репозитория и переделать второй, добавив много информации из первого. По сути, мы пишем конвертер, преобразующий данные в нужный вид.
Более детальное описание
Предположим, у вас есть:
- Репозитории с микросервисами, в которых каждый микросервис имеет свой Openshift шаблон, включающий DeploymentConfig, Route, Service и различные Istio операторы.
- Репозиторий с Inventory, где для разных стендов вы храните ConfigMaps и Secrets для каждого приложения.
Однажды вы решаете перенести все свои проекты на Helm. Это позволит вам параметризировать различные сущности и добавить опции. Например, указав в Inventory «istio: false», вы можете отключить Istio одной строкой, вместо редактирования каждого Openshift шаблона.
Пусть вы уже создали Helm чарты, учитывающие различия ваших шаблонов. Теперь ваша задача — анализировать шаблон каждого сервиса с его спецификой, объектами и переделать инвентарь, добавив Helm параметры для каждого сервиса так, чтобы при деплое ничего не изменилось.
Реализация
Как раз для анализа и хранении информации о файле мы можем воспользоваться датаклассом.
Если кратко о разнице с обычными классами:
- Синтаксис: Датаклассы используют декоратор
@dataclass
, введенный в Python 3.7, для определения класса, в то время как обычные классы определяются ключевым словомclass
. - Автоматическая генерация методов: Датаклассы автоматически генерируют ряд методов, таких как
__init__
,__repr__
,__eq__
и т. д., основанных на аннотациях полей. В обычных классах эти методы нужно определять вручную. - Наследование: Обычные классы поддерживают наследование, тогда как датаклассы не наследуются. Однако, датаклассы могут использовать наследование атрибутов, определенных в обычных классах.
- Назначение: Датаклассы предназначены для хранения данных, тогда как обычные классы могут выполнять любые задачи.
Создаем датакласс Template.py
from dataclasses import dataclass
@dataclass
class Template:
file: str = None
appName: str = None
containerPort: str = None
jaegerEnabled: bool = None
host: str = None
path: str = None
# и прочие атрибуты которые мы хотим выяснить в ходе нашего анализа
def __post_init__(self):
self.parse()
def parse(self) -> None:
with open(self.file) as tmpl:
content = yaml.load(tmpl)
for param in content["parameters"]:
if param["name"] == "APP_PORT":
self.containerPort = param["value"]
if param["name"] == "APP_NAME":
self.appName = param["value"]
for obj in content["objects"]:
self.objects.append(obj["kind"])
if obj["kind"] == "DeploymentConfig":
# JAEGER
for container in obj["spec"]["template"]["spec"]["containers"]:
self.jaegerEnabled = "jaeger" in container["image"]
if self.jaegerEnabled:
break
for container in obj["spec"]["template"]["spec"]["containers"]:
if container['name'] == self.appName:
if obj["kind"] == "Route":
self.path = obj["spec"]["path"]
self.host = obj["spec"]["host"]
# и так далее
В атрибутах датакласса мы описываем все атрибуты, которыми характерезуется наш анализируемый файл. Имя пода, порт приложения, адрес хоста, включен ли истио в этом шаблоне, подключены ли ConfigMaps и Secrets.
Далее создаем внутри датакласса функцию parse, где мы считываем yaml файл переданный при инициализации класса, и достаем из него всю нужную информацию, обновляя наши атрибуты.
Осталось лишь добавить нашу функцию parse в метод __post_init__ чтобы функция автоматически запустилась после инициализации.
Использование
Теперь, проходясь по нашим yaml файлам, мы можем сделать из них объекты с проанализированной информацией и сразу воспользоваться ей. Или, как в примере ниже — сохранить и воспользоваться потом:
data = {}
for filepath in что_угодно:
data[filepath] = Template(file=filepath)
print(data['myapp.yaml'].jaegerEnabled)
Теперь мы можем использовать наши данные для любых целей, например для заполнения jija шаблонов и прочего.
Итог
Если попробовать ответить на вопрос «ЗАЧЕМ?», ведь с одной стороны можно было бы использовать сложные и гигатские массивы. Хотя бы просто для того, что использование хотя бы датаклассов дает вам возможность куда проще отлаживать и сопровождать код.
Это повышает читаемость, и когда в ходе разработки вам понадобится что-то изменить в «анализирующем» механизме, скорее всего вам будет сложнее в этом заблудится и вы потратите меньше времени.
А на вопрос — «КАК?» я уже ответил. 🙂