Можно ли группировать переменные в классе данных python?

#python #json #http #struct #python-dataclasses

Вопрос:

Я искал, но не нашел хорошего ответа, поэтому я опубликую его 🙂

В настоящее время я создаю модуль python, который использует http-запрос get для извлечения объекта с кучей данных, которые структурированы следующим образом.

  • Основная группа
    • Группа 1
      • данные 1
      • данные 2
    • Группа 2
      • данные 1
      • данные 2
    • Группа 3
      • данные 1
      • данные 2

Я создал класс данных, в котором просто перечислены все эти переменные, такие как

 @dataclass
class MyData:
  grp1_data1: str
  grp1_data2: str
  grp2_data1: str
  grp2_data2: str
  grp3_data1: str
  grp3_data2: str

@classmethod
def from_dict(cls, data: dict) -> "MyData":
    return cls(
      grp1_data1=data["Main group"]["Group 1"]["data1"],
      grp1_data2=data["Main group"]["Group 1"]["data2"],
      # And so on ...
    )
 

То, что я ищу, — это способ сгруппировать переменные внутри класса данных по аналогии со структурой, чтобы мне не нужно было смешивать имя группы и имя данных в имени переменной.

Я довольно новичок в python и не знаю, что это за групповые функции, которые работают с классами данных, если таковые имеются?

Я хотел бы иметь возможность написать что-то подобное grp1.data1=data["Main group"]["Group 1"]["data1"] или подобное.

Комментарии:

1. Создайте два класса данных, один из которых представляет одну группу, а другой содержит несколько экземпляров этого класса данных группы…?

Ответ №1:

Можно создавать многоуровневые классы данных, чтобы делать то, что вы хотите (возможно, не так элегантно, как структуры типа C, но это работает), используя композицию классов:

 @dataclass
class Top:
    
    @dataclass
    class Child:
        data1: str
        data2: str
            
    Group1: Child
    Group2: Child
    Group3: Child
        
        
inst = Top(
    Group1=Top.Child('a','b'),
    Group2=Top.Child('x', 'y'),
    Group3=Top.Child('101', '102')
)

# check it:
@dataclass
class Top:
    
    @dataclass
    class Child:
        data1: str
        data2: str
            
    Group1: Child
    Group2: Child
    Group3: Child
        

# create an instance
inst = Top(
    Group1=Top.Child('a','b'),
    Group2=Top.Child('x', 'y'),
    Group3=Top.Child('101', '102')
)

# check it:
assert inst.Group2.data2 == 'y'
 

Ключ в том, что вы также должны определить все дочерние элементы как классы данных (или, правильнее, как классы).
Вы можете определить дочерние классы на месте(как указано выше) или отдельно.

Комментарии:

1. Идеально, это именно то, что я искал! Спасибо!

Ответ №2:

Ваш вопрос немного неясен, но, как указано в комментариях, было бы лучше иметь единую модель в качестве класса данных, которая представляет данные вашей группы (т. Е. Модель, содержащую data1 data2 поля и), и определить вспомогательную функцию, которая создает сопоставление имени группы экземплярам модели, как показано ниже.

Примечание: Это предполагает, что вы используете Python 3.8 . Для более ранних версий я бы сделал две вещи:

  • __future__ При необходимости удалите импорт, а вместо этого импортируйте Type и Dict из typing модуля, так как встроенные типы не поддерживают подписанные значения в Python 3.8 или более ранних версиях.
  • Удалите использование оператора walrus := , который был введен в Python 3.8, и вместо этого используйте следующую за ним строку с комментариями.
 # Future import to allow the `int | str` syntax below
# Can be removed for Python 3.10
from __future__ import annotations

from dataclasses import dataclass
from typing import TypeVar


# Create a type that can be `MyData`, or any subclass
D = TypeVar('D', bound='MyData')


@dataclass
class MyData:
    data1: str
    data2: str

    @classmethod
    def from_dict(cls: type[D], data: dict, group_num: int | str) -> D:
        return cls(
            data1=data['MG'][f'G {group_num}']['data1'],
            data2=data['MG'][f'G {group_num}']['data2'],
        )

    @classmethod
    def group_to_data(cls: type[D], data: dict) -> dict[int, D]:
        return {(group_num := int(group_key.split()[-1])): cls.from_dict(
                    data, group_num)
                for group_key in data['MG']}

        # For Python 3.7 or lower, uncomment and use the below instead
        # ret_dict = {}
        # for group_key in data['MG']:
        #     group_num = int(group_key.split()[-1])
        #     ret_dict[group_num] = cls.from_dict(data, group_num)
        #
        # return ret_dict
 

Код для тестирования:

 def main():
    from pprint import pprint

    my_data = {
        'MG': {
            'G 1': {
                'data1': 'hello',
                'data2': 'World!',
            },
            'G 2': {
                'data1': '',
                'data2': 'Testing',
            },
            'G 3': {
                'data1': 'hello 123',
                'data2': 'world 321!'
            }
        }
    }

    group_to_data = MyData.group_to_data(my_data)
    pprint(group_to_data)

    # True
    assert group_to_data[1] == MyData('hello', 'World!')
 

Выход:

 {1: MyData(data1='hello', data2='World!'),
 2: MyData(data1='', data2='Testing'),
 3: MyData(data1='hello 123', data2='world 321!')}