Python 如何忽略传递给数据类的额外参数?
我想创建一个Python 如何忽略传递给数据类的额外参数?,python,python-3.x,python-dataclasses,Python,Python 3.x,Python Dataclasses,我想创建一个configdataclass,以简化特定环境变量的白名单和访问(键入os.environ['VAR\u NAME']相对于config.VAR\u NAME来说是一件乏味的事情)。因此,我需要忽略我的数据类的\uuuuu init\uuuu函数中未使用的环境变量,但我不知道如何提取默认的\uuuu init\uuuu,以便用一个同样包含*\uuu作为参数之一的函数来包装它 import os from dataclasses import dataclass @dataclass
config
dataclass
,以简化特定环境变量的白名单和访问(键入os.environ['VAR\u NAME']
相对于config.VAR\u NAME
来说是一件乏味的事情)。因此,我需要忽略我的数据类
的\uuuuu init\uuuu
函数中未使用的环境变量,但我不知道如何提取默认的\uuuu init\uuuu
,以便用一个同样包含*\uuu
作为参数之一的函数来包装它
import os
from dataclasses import dataclass
@dataclass
class Config:
VAR_NAME_1: str
VAR_NAME_2: str
config = Config(**os.environ)
运行此命令会给我带来
TypeError:\uuuuu init\uuuuuuuuuuuuuuuuuuuuuuuu()得到一个意外的关键字参数“SOME\u DEFAULT\u ENV\u VAR”
我只会提供一个显式的\uuuuuuuuuuuu init\uuuuuuuuuuuuuuuuuuu
。循环体仅设置可识别的值,忽略意外值
不过,请注意,这不会抱怨没有默认值的值丢失,直到稍后
@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)
或者,您可以将筛选的环境传递给默认的配置
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})
在将参数列表传递给构造函数之前清理它可能是最好的方法。不过,我建议不要编写自己的\uuuuu init\uuuuu
函数,因为dataclass'\uuuuuuu init\uuuuu
会做一些其他方便的事情,如果覆盖它,您会丢失这些事情
此外,由于参数清理逻辑与类的行为紧密绑定并返回实例,因此将其放入类方法中可能是有意义的:
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')
我使用了两种答案的组合setattr
可能是性能杀手。当然,如果字典在dataclass中没有一些记录,您需要为它们设置字段默认值
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})
是的,这是我关心的问题,看起来函数有点复杂,有一些检查等(但我只看了一秒钟)。有没有办法把自动生成的函数撕下来包装一下?我也不希望其他环境变量也在其中;你想替换它。也就是说,在调用默认的\uuuu init\uuuuu
:c=Config({k:v代表k,v代表kwargs,如果k在set中(f.name代表f在dataclasses.fields(Config))})之前过滤环境映射)
在初始化实例之前过滤参数非常有效!如果你把这个问题单独回答,我会接受的。最后的代码是:从数据类导入数据类,字段
config=config(**{k:v代表k,v代表os.environ.items(),如果k在set中(f.name代表f在fields(config))}
。不要使用cls.\uu注释\uu
,使用dataclass.fields()
这样你就可以内省它们的配置(例如忽略init=False
字段)但是在这个上下文中您需要InitVar
s,不是吗?它们也会被dataclasses.fields()跳过
,所以这里可能还有更多的问题需要解决。@MartijnPieterscls.\uuuu dataclass\u fields\uuu
与InitVar
包含一起工作,并且可以访问init
字段。不幸的是,映射还包括ClassVar
字段,并且这些字段的init
标志没有设置为False
at不是我的意思。inspect.signature()
将为您提供一个signature
实例,它将允许您轻松创建一组可接受的参数名称。