Я работаю с некоторыми патчами 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"
Два патча имеют одинаковую структуру, но совершенно разные значения. Все эти значения являются статическими, за исключением одного аргумента, например. имя ветки или имя приложения.
🤔 А знаете ли вы, что...
С Python можно создавать ботов для социальных сетей и мессенджеров.
Вы можете использовать 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)
Вместо производных классов вы можете хранить 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)
...