Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does one ignore extra arguments passed to a data class?

Tags:

I'd like to create a config dataclass in order to simplify whitelisting of and access to specific environment variables (typing os.environ['VAR_NAME'] is tedious relative to config.VAR_NAME). I therefore need to ignore unused environment variables in my dataclass's __init__ function, but I don't know how to extract the default __init__ in order to wrap it with, e.g., a function that also includes *_ as one of the arguments.

import os from dataclasses import dataclass  @dataclass class Config:     VAR_NAME_1: str     VAR_NAME_2: str  config = Config(**os.environ) 

Running this gives me TypeError: __init__() got an unexpected keyword argument 'SOME_DEFAULT_ENV_VAR'.

like image 674
Californian Avatar asked Feb 13 '19 19:02

Californian


People also ask

What is a Dataclass?

A data class is a class typically containing mainly data, although there aren't really any restrictions. It is created using the new @dataclass decorator, as follows: from dataclasses import dataclass @dataclass class DataClassCard: rank: str suit: str.

What does @dataclass do in Python?

Python introduced the dataclass in version 3.7 (PEP 557). The dataclass allows you to define classes with less code and more functionality out of the box.

What is __ Post_init __?

The post-init function is an in-built function in python and helps us to initialize a variable outside the __init__ function. post-init function in python.

Can a Dataclass have methods?

A dataclass can very well have regular instance and class methods. Dataclasses were introduced from Python version 3.7. For Python versions below 3.7, it has to be installed as a library.


2 Answers

Cleaning the argument list before passing it to the constructor is probably the best way to go about it. I'd advice against writing your own __init__ function though, since the dataclass' __init__ does a couple of other convenient things that you'll lose by overwriting it.

Also, since the argument-cleaning logic is very tightly bound to the behavior of the class and returns an instance, it might make sense to put it into a classmethod:

from dataclasses import dataclass import 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') 
like image 142
Arne Avatar answered Sep 29 '22 12:09

Arne


I would just provide an explicit __init__ instead of using the autogenerated one. The body of the loop only sets recognized value, ignoring unexpected ones.

Note that this won't complain about missing values without defaults until later, though.

@dataclass(init=False) class Config:     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) 

Alternatively, you can pass a filtered environment to the default 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}) 
like image 23
chepner Avatar answered Sep 29 '22 14:09

chepner