Как можно игнорировать дополнительные аргументы, передаваемые в класс данных? - PullRequest
0 голосов
/ 13 февраля 2019

Я хотел бы создать config dataclass, чтобы упростить внесение в белый список и доступ к определенным переменным среды (ввод os.environ['VAR_NAME'] утомителен относительно config.VAR_NAME).Поэтому мне нужно игнорировать неиспользуемые переменные окружения в моей функции dataclass __init__, но я не знаю, как извлечь значение по умолчанию __init__, чтобы обернуть его, например, функцией, которая также включает *_ в качестве одного из аргументов.

import os
from dataclasses import dataclass

@dataclass
class Config:
    VAR_NAME_1: str
    VAR_NAME_2: str

config = Config(**os.environ)

Запуск этого дает мне TypeError: __init__() got an unexpected keyword argument 'SOME_DEFAULT_ENV_VAR'.

Ответы [ 3 ]

0 голосов
/ 11 марта 2019

Очистка списка аргументов перед передачей его конструктору, вероятно, лучший способ сделать это.Однако я бы не советовал писать собственную функцию __init__, так как класс данных '__init__ делает пару других удобных вещей, которые вы потеряете, переписав его.

Кроме того, поскольку очистка аргументовлогика очень тесно связана с поведением класса и возвращает экземпляр, возможно, имеет смысл поместить его в classmethod:

from dataclasses import dataclass
from inspect

@dataclass
class Config:
    var_1: str
    var_2: str

    @classmethod
    def from_dict(cls, env):      
        return cls(**{
            k: v for k, v in env.items() 
            if k in inspect.signature(cls).parameters
        })


# usage:
params = {'var_1': 'a', 'var_2': 'b', 'var_3': 'c'}
c = Config.from_dict(params)   # works without raising a TypeError 
print(c)
# prints: Config(var_1='a', var_2='b')
0 голосов
/ 25 июля 2019

Я использовал комбинацию обоих ответов;setattr может быть убийцей производительности.Естественно, если в словаре не будет записей в классе данных, вам нужно будет установить для них значения по умолчанию.

from __future__ import annotations
from dataclasses import field, fields, dataclass

@dataclass()
class Record:
    name: str
    address: str
    zip: str = field(default=None)  # won't fail if dictionary doesn't have a zip key

    @classmethod
    def create_from_dict(cls, dict_) -> Record:
        class_fields = {f.name for f in fields(cls)}
        return Record(**{k: v for k, v in dict_.items() if k in class_fields})
0 голосов
/ 13 февраля 2019

Я бы просто предоставил явный __init__ вместо использования автоматически сгенерированного.Тело цикла только устанавливает распознанное значение, игнорируя неожиданные.

Обратите внимание, что это не будет жаловаться на пропущенные значения без значений по умолчанию, хотя позже.

@dataclass
class Config(init=False):
    VAR_NAME_1: str
    VAR_NAME_2: str

    def __init__(self, **kwargs):
        names = set([f.name for f in dataclasses.fields(self)])
        for k, v in kwargs.items():
            if k in names:
                setattr(self, k, v)

В качестве альтернативы, вы можетепередать отфильтрованную среду по умолчанию Config.__init__.

field_names = set(f.name for f in dataclasses.fields(Config))
c = Config({k:v for k,v in os.environ.items() if k in field_names})
...