Использование питона 3.10.4
Привет всем, я собираю скрипт, в котором я читаю файл yaml с информацией о кластере k8s, и я хотел бы обрабатывать загруженный yaml как классы данных, чтобы я мог ссылаться на них со свойствами .
.
Пример ямла:
account: 12345
clusters:
- name: cluster_1
endpoint: https://cluster_2
certificate: abcdef
- name: cluster_1
endpoint: https://cluster_2
certificate: abcdef
А вот мой скрипт для загрузки и доступа к нему:
import yaml
from dataclasses import dataclass
@dataclass
class ClusterInfo:
_name: str
_endpoint: str
_certificate: str
@dataclass
class AWSInfo:
_account: int
_clusters: list[ClusterInfo]
clusters = yaml.safe_load(open('D:\git\clusters.yml', 'r'))
a = AWSInfo(
_account=clusters['account'],
_clusters=clusters['clusters']
)
print(a._account) #prints 12345
print(a._clusters) #prints the dict of both clusters
print(a._clusters[0]) #prints the dict of the first cluster
#These prints fails with AttributeError: 'dict' object has no attribute '_endpoint'
print(a._clusters[0]._endpoint)
for c in a._clusters:
print(c._endpoint)
Итак, мой вопрос: что я делаю неправильно на последних отпечатках? Как я могу получить доступ к свойствам каждого члена в массиве объектов класса данных?
Вам нужно преобразовать clusters['clusters']
в список ClusterInfo
, когда вы звоните AWSInfo()
.
очень важно понимать, что «класс данных» — это не тип. dataclasses.dataclass
— это генератор кода, результатом которого является обычный класс, создающий обычные экземпляры.
Кроме того, почему перед каждым атрибутом стоит один символ подчеркивания??
@juanpa.arrivillaga Я видел это в другом примере класса данных, и я полагаю, что это сделано для предотвращения случайного присвоения имени свойству встроенного свойства.
Нет! Не делай этого.
Как отмечает Бармар в комментарии, даже если вы правильно ввели ключ _clusters
в свой класс данных AWSInfo
...
@dataclass
class AWSInfo:
_account: int
_clusters: list[ClusterInfo]
... модуль dataclasses
недостаточно умен, чтобы автоматически преобразовывать элементы списка clusters
во входных данных в соответствующий тип данных. Если вы используете более полную библиотеку моделей данных, такую как Pydantic, все будет работать так, как вы ожидаете:
import yaml
from pydantic import BaseModel
class ClusterInfo(BaseModel):
name: str
endpoint: str
certificate: str
class AWSInfo(BaseModel):
account: int
clusters: list[ClusterInfo]
with open('clusters.yml', 'r') as fd:
clusters = yaml.safe_load(fd)
a = AWSInfo(**clusters)
print(a.account) #prints 12345
print(a.clusters) #prints the dict of both clusters
print(a.clusters[0]) #prints the dict of the first cluster
#These prints fails with AttributeError: 'dict' object has no attribute '_endpoint'
print(a.clusters[0].endpoint)
for c in a.clusters:
print(c.endpoint)
Запуск приведенного выше кода (с вашим образцом ввода) дает:
12345
[ClusterInfo(name='cluster_1', endpoint='https://cluster_2', certificate='abcdef'), ClusterInfo(name='cluster_1', endpoint='https://cluster_2', certificate='abcdef')]
name='cluster_1' endpoint='https://cluster_2' certificate='abcdef'
https://cluster_2
https://cluster_2
https://cluster_2
«недостаточно умен» -> «выходит за рамки»
Я думаю, что такое поведение нарушает «принцип наименьшего удивления», поэтому нам просто придется расходиться в этом вопросе.
@larsks спасибо, что показали мне, что я сделал не так. Я изменил код, и он работал в соответствии с вашим предложением, но другой ответ о мастерах данных я использовал только потому, что он был более естественным образом создан для того, что я ищу. Спасибо за помощь!
Модуль dataclasses
не предоставляет встроенной поддержки для этого варианта использования, то есть для загрузки данных YAML во вложенную модель класса.
В таком случае я бы обратился к библиотеке ser/de, такой как dataclass-wizard , которая обеспечивает поддержку OOTB для (де)сериализации данных YAML через библиотеку PyYAML.
Отказ от ответственности: я являюсь создателем и хранителем этой библиотеки.
Примечание. Вероятно, мне потребуется упростить этот шаг для создания модели класса данных для данных YAML. Возможно, стоит создать проблему для изучения, пока позволяет время. В идеале использование осуществляется из CLI, однако, поскольку у нас есть данные YAML, это сложно, поскольку служебный инструмент ожидает JSON.
На данный момент проще всего сделать это в самом Python:
from json import dumps
# pip install PyYAML dataclass-wizard
from yaml import safe_load
from dataclass_wizard.wizard_cli import PyCodeGenerator
yaml_string = """
account: 12345
clusters:
- name: cluster_1
endpoint: https://cluster_2
certificate: abcdef
- name: cluster_1
endpoint: https://cluster_2
certificate: abcdef
"""
py_code = PyCodeGenerator(experimental=True, file_contents=dumps(safe_load(yaml_string))).py_code
print(py_code)
Отпечатки:
from __future__ import annotations
from dataclasses import dataclass
from dataclass_wizard import JSONWizard
@dataclass
class Data(JSONWizard):
"""
Data dataclass
"""
account: int
clusters: list[Cluster]
@dataclass
class Cluster:
"""
Cluster dataclass
"""
name: str
endpoint: str
certificate: str
Содержимое my_file.yml
:
account: 12345
clusters:
- name: cluster_1
endpoint: https://cluster_5
certificate: abcdef
- name: cluster_2
endpoint: https://cluster_7
certificate: xyz
Код Python:
from __future__ import annotations
from dataclasses import dataclass
from pprint import pprint
from dataclass_wizard import YAMLWizard
@dataclass
class Data(YAMLWizard):
account: int
clusters: list[Cluster]
@dataclass
class Cluster:
name: str
endpoint: str
certificate: str
data = Data.from_yaml_file('./my_file.yml')
pprint(data)
for c in data.clusters:
print(c.endpoint)
Результат:
Data(account=12345,
clusters=[Cluster(name='cluster_1',
endpoint='https://cluster_5',
certificate='abcdef'),
Cluster(name='cluster_2',
endpoint='https://cluster_7',
certificate='xyz')])
https://cluster_5
https://cluster_7
Спасибо за это, это было идеальное решение!
не беспокойся! рад, что это было полезно в вашем случае использования.
a._clusters
— это список словарей, а не списокClusterInfo
объектов.