Я работаю с некоторыми патчами YAML. Эти патчи имеют схожую структуру, но содержат разные значения. Значения часто трудно запомнить, поэтому я хочу абстрагировать их в экземпляры классов, на которые я могу ссылаться.
Вот подход, который я использовал до сих пор:
class YamlPatch:
def __init__(self, kind, name, namespace, op, path, value):
target = {
"kind": kind,
"name": name,
"namespace": namespace
},
scalar=[{
"op": op,
"path": path,
"value": value
}]
self.yaml = (target, scalar)
class PatchA(YamlPatch):
def __init__(self, name):
namespace = "my-namespace"
kind = "test"
op = "replace"
path = "/test"
value = "hello"
super().__init__(kind, name, namespace, op, path, value)
class PatchB(YamlPatch):
def __init__(self, path):
namespace = "my-namespace"
name = "my-name"
kind = "test"
op = "replace"
value = "hello"
super().__init__(kind, name, namespace, op, path, value)
### Insert 4 or 5 other types of patches here...
patches = []
patches.append(PatchA("hello").yaml)
for app in ["app1", "app2"]:
patches.append(PatchB(f"/{app}").yaml)
print(patches)
### output: [(({'kind': 'test', 'name': 'hello', 'namespace': 'my-namespace'},), [{'op': 'replace', 'path': '/test', 'value': 'hello'}]), (({'kind': 'test', 'name': 'my-name', 'namespace': 'my-namespace'},), [{'op': 'r
eplace', 'path': '/app1', 'value': 'hello'}]), (({'kind': 'test', 'name': 'my-name', 'namespace': 'my-namespace'},), [{'op': 'replace', 'path': '/app2', 'value': 'hello'}])]
Это кажется беспорядочным и повторяющимся, особенно когда вы добавляете подсказки и комментарии. Не очень СУХОЙ. Некоторые значения являются довольно распространенными значениями по умолчанию, и необходимость __init__
затем super()
в каждом дочернем классе (патче) неприятна.
Я пробовал использовать классы данных, но поскольку необходимые «входные» аргументы для дочерних классов разные, мне пришлось бы использовать аргумент kw_only
, который сложно запомнить при таком большом количестве разных патчей (например, PatchA(value = "blah")
или это было PatchA(name = "blah")
, я могу' не помнишь?).
Короче говоря, я ищу самый быстрый и эффективный способ написания кода, который позволит мне ссылаться на запоминающееся и простое имя (здесь я назвал их PatchA
и PatchB
, но в реальном коде они будут чем-то уникальным и очевидным для пользователя). сопровождающие) и вернуть правильно отформатированный патч YAML. Например. print(PatchA)
.
Я использую Python 3.11.
--- ИЗМЕНИТЬ ДЛЯ РАЗЪЯСНЕНИЯ
Причина, по которой я хочу избежать использования аргументов ключевых слов, заключается в том, что каждый патч имеет свой набор значений, и их нелегко запомнить. Кроме того, если значения изменяются, я хочу иметь возможность изменять их в одном месте, а не каждый раз, когда на них ссылаются в моем коде.
Вот реалистичный (хотя и сокращенный) пример:
class YamlPatch:
yaml = ...
class PackageApplicationFromGit(YamlPatch):
path = "/spec/path"
name = f"application-{application}"
value = f"/some/applications/path/{application}"
class AppsGitRepoBranchPatch(YamlPatch):
kind = "GitRepository"
path = "/spec/ref/branch"
value = "my-branch-name"
Два патча имеют одинаковую структуру, но совершенно разные значения. Все эти значения являются статическими, за исключением одного аргумента, например. имя ветки или имя приложения.
Я не полностью следую @OysterShucker - вы имеете в виду просто создать стандартные переменные, не относящиеся к классу, а затем ссылаться на них? можете ли вы импортировать переменные из отдельного модуля? если да, то я думаю, что это может быть простой ответ здесь
Я отредактировал свой ответ, приведя пример
Вы можете использовать dataclass
и исключить подклассы.
from dataclasses import dataclass
@dataclass
class YamlPatch:
namespace :str = "my-namespace"
kind :str = "test"
name :str = "my-name"
op :str = "replace"
path :str = "/test"
value :str = "hello"
def __post_init__(self) -> None:
target = {
"namespace": self.namespace ,
"kind" : self.kind ,
"name" : self.name ,
}
scalar=[{
"op" : self.op ,
"path" : self.path ,
"value": self.value ,
}]
self.yaml = (target, scalar)
def __str__(self) -> str:
return str(self.yaml)
if __name__ == "__main__":
patches = [YamlPatch(name = "hello").yaml]
for app in ("app1", "app2"):
patches.append(YamlPatch(path=f"/{app}").yaml)
print(*patches, sep = "\n")
Честно говоря, единственное, что dataclass
здесь действительно делает для вас, — это избавляет вас от необходимости выполнять всю стандартную работу в методе __init__
для преобразования аргументов конструктора в свойства. Это также делает его намного чище, поскольку вам не понадобится конструктор с шестью типизированными аргументами по умолчанию. Если ваши экземпляры YamlPatch
должны быть более динамичными, вы можете реорганизовать __post_init__
в property
.
from dataclasses import dataclass
@dataclass
class YamlPatch:
namespace :str = "my-namespace"
kind :str = "test"
name :str = "my-name"
op :str = "replace"
path :str = "/test"
value :str = "hello"
@property
def yaml(self) -> tuple:
target = {
"namespace": self.namespace ,
"kind" : self.kind ,
"name" : self.name ,
}
scalar=[{
"op" : self.op ,
"path" : self.path ,
"value": self.value ,
}]
return (target, scalar)
def __str__(self) -> str:
return str(self.yaml)
Я пробовал использовать классы данных, но поскольку необходимые «входные» аргументы поскольку дочерние классы разные, мне придется использовать kw_only аргумент, который сложно запомнить при таком большом количестве разных патчи (например, PatchA(value="blah") или это был PatchA(name="blah"), я не помню?).
В вашем примере все экземпляры имеют одинаковые свойства, независимо от того, как вы подклассифицировали супер, поэтому ваш аргумент KW_ONLY
не имеет смысла. Что касается запоминания значений отдельных патчей, вам обязательно следует их запомнить. Кажется, вы ищете систему, в которой вы произвольно просто соединяете вещи и/или имеете множество произвольных классов, названных в честь значения, каждый из которых принимается. Это излишне раздуто, и у вас будет столько же проблем с отслеживанием всех классов, сколько и с отслеживанием необходимого значения. Короче говоря: ленивый подход более громоздкий, чем просто отсутствие лени. Вы уже доказали это себе и признали это своей попыткой.
Пример из моего комментария. Не таблица поиска, но по сути те же результаты.
# you have to figure out where `application` is coming from
PackageApplicationFromGit = dict(
path = "/spec/path",
name = f"application-{application}",
value = f"/some/applications/path/{application}",
)
AppsGitRepoBranchPatch = dict(
kind = "GitRepository",
path = "/spec/ref/branch",
value = "my-branch-name",
)
patch = YamlPatch(**AppsGitRepoBranchPatch)
Хорошо, мне нравится идея иметь один класс YamlPatch, но это приводит меня на территорию того фрагмента, который вы только что отредактировали... :) Посмотрим, смогу ли я объяснить немного более понятно. если у меня есть патч под названием PackageApplicationFromGit
, я знаю - по его названию - что единственное значение, которое мне нужно указать, - это имя приложения, например. PackageApplicationFromGit("grafana")
. Однако в вашем примере мне нужно было бы запомнить, какую часть патча обновить словом grafana
... что даже сейчас, слетя с ума, я не могу
Я надеялся, что смогу сделать что-то, где имя класса («тип» патча, который я использую) принимает всего один аргумент и помещает его в нужное место.
@turbonrd - Ты можешь это сделать. Это просто плохая идея. Вы наверняка должны знать, что такое «графана». Я склонен подходить к вещам «реального мира». Реальный эквивалент того, что вы хотите, — это все равно, что не помнить, зовут ли меня Майкл, 49 лет, или Новый Орлеан.
возможно, мне следовало использовать реальные примеры, но все гораздо сложнее. Я помню слово «графана», но не могу вспомнить соответствующие ему строки path
или value
. Сейчас я обновлю свой ОП реальным примером, возможно, это будет иметь больше смысла.
@turbonrd — Вы можете просто создать таблицу поиска аргументов по умолчанию для всех «сервисов», которые вы поддерживаете, и: YamlPatch(**lookup.get('this_service', {}))
Я добавил пример из реальной жизни, который может объяснить лучше. Вероятно, мне следовало сделать это в первую очередь, но я пытался снизить сложность вопроса. и тем самым усложнили задачу :rolleyes:
Хорошо, отлично. Думаю, это самое близкое к тому, что у меня было в голове. спасибо за ваши усилия :)
Вместо производных классов вы можете хранить dict
изменяемые параметры и значения по умолчанию dict
.
default = {'kind': 'test', 'name': 'my-name', 'namespace': 'my-namespace',
'op': 'replace', 'path': '/test', 'value': 'hello'}
patches = {
'patchA': ['name'],
'patchB': ['path'],
'patchC': ['namespace', 'value']
}
class YamlPatch:
def __init__(self, patch, *args):
values = default.copy()
for i, var in enumerate(patches[patch]):
values[var] = args[i]
target = dict(list(values.items())[:3]),
scalar = [dict(list(values.items())[3:])]
self.yaml = (target, scalar)
pat = []
pat.append(YamlPatch('patchA', 'hello').yaml)
for app in ['app1', 'app2']:
pat.append(YamlPatch('patchB', f'/{app}').yaml)
pat.append(YamlPatch('patchC', 'outerspace', 'goodbye').yaml)
print(pat)
Выход
[(({'kind': 'test', 'name': 'hello', 'namespace': 'my-namespace'},), [{'op': 'replace', 'path': '/test', 'value': 'hello'}]),
(({'kind': 'test', 'name': 'my-name', 'namespace': 'my-namespace'},), [{'op': 'replace', 'path': '/app1', 'value': 'hello'}]),
(({'kind': 'test', 'name': 'my-name', 'namespace': 'my-namespace'},), [{'op': 'replace', 'path': '/app2', 'value': 'hello'}]),
(({'kind': 'test', 'name': 'my-name', 'namespace': 'outerspace'},), [{'op': 'replace', 'path': '/test', 'value': 'goodbye'}])]
Если у вас нет случая с несколькими значениями, его можно упростить до
...
patches = {
'patchA': 'name',
'patchB': 'path',
}
class YamlPatch:
def __init__(self, patch, value):
values = default.copy()
values[patches[patch]] = value
target = dict(list(values.items())[:3]),
scalar = [dict(list(values.items())[3:])]
self.yaml = (target, scalar)
...
спасибо @Guy - хороший ответ; лаконично, но оно упирается в трудности, о которых я только что упомянул в своем ОП.
@turbonrd - так или иначе вам придется что-то вспомнить. Вы пытаетесь изобрести систему Альцгеймера! :D
Я помню одну вещь, но не все остальное, что с ней связано!
Просто превратите их в словари и передайте как
**kwargs
в класс данных, который я вам предоставил. Как было предложено в моем последнем комментарии под моим ответом.