如果您希望数据类接受任意额外的关键字参数,那么您必须定义自己的__init__ 方法,或者在metaclass 上提供自定义__call__ 方法。如果你定义了一个自定义的__init__ 方法,dataclass 装饰器不会为你生成一个;此时无需再使用__post_init__,因为您已经在编写__init__ 方法。
旁注:
-
__new__ 无法更改传递给 __init__ 的参数。元类的__call__ 通常会首先调用cls.__new__(<arguments>),然后在instance 从__new__ 返回值上调用instance.__init__(<arguments>,参见datamodel documentation。
- 你不能使用
int or None,这是一个只返回int的表达式,它不会让你省略age参数。改为为该字段提供默认值,或者如果 None 仅用于指示 age=0 或 int() 转换失败,则使用 Union 类型提示。
- 定义了默认值的字段必须在未定义默认值的字段之后,因此请在末尾添加
age。
- 如果您还在数据类之外使用类型提示,并且
age 是一个可选字段,则使用typing.Optional 将age 字段的类型正确标记为可选。 Optional[int] 等价于Union[int, None];当没有设置默认值并且省略 age 是不可接受的时,我个人更喜欢在构造函数中使用后者。
- 使用
isinstance() 确定对象是否为字符串。或者干脆不测试,因为int(self.age) 只是返回不变的self.age,如果它已经设置为一个整数。
- 如果可以将年龄设置为
0 设置为None,则仅在__post_init__ 方法中使用or None。
- 如果仅当
int(age) 失败时将age 设置为None,那么您必须使用try:...except 来处理ValueError 或TypeError 在这种情况下可能引发的int() 异常,而不是or None。
假设您的意思是仅在转换失败时将 age 设置为 None:
from dataclasses import dataclass
from typing import Union
@dataclass
class Employee(object):
name: str
lastname: str
age: Union[int, None] # set to None if conversion fails
salary: int
department: str
def __init__(
self,
name: str,
lastname: str,
age: Union[int, None],
salary: int,
department: str,
*args: Any,
**kwargs: Any,
) -> None:
self.name = name
self.lastname = lastname
try:
self.age = int(age)
except (ValueError, TypeError):
# could not convert age to an integer
self.age = None
self.salary = salary
self.department = department
def __str__(self):
return f'{self.name}, {self.lastname}, {self.age}'
如果你想走元类路线,那么你可以通过自省__init__ 或__new__ 方法调用签名来创建一个忽略几乎任何类的所有额外参数的方法: p>
from inspect import signature, Parameter
class _ArgTrimmer:
def __init__(self):
self.new_args, self.new_kw = [], {}
self.dispatch = {
Parameter.POSITIONAL_ONLY: self.pos_only,
Parameter.KEYWORD_ONLY: self.kw_only,
Parameter.POSITIONAL_OR_KEYWORD: self.pos_or_kw,
Parameter.VAR_POSITIONAL: self.starargs,
Parameter.VAR_KEYWORD: self.starstarkwargs,
}
def pos_only(self, p, i, args, kwargs):
if i < len(args):
self.new_args.append(args[i])
def kw_only(self, p, i, args, kwargs):
if p.name in kwargs:
self.new_kw[p.name] = kwargs.pop(p.name)
def pos_or_kw(self, p, i, args, kwargs):
if i < len(args):
self.new_args.append(args[i])
# drop if also in kwargs, otherwise parameters collide
# if there's a VAR_KEYWORD parameter to capture it
kwargs.pop(p.name, None)
elif p.name in kwargs:
self.new_kw[p.name] = kwargs[p.name]
def starargs(self, p, i, args, kwargs):
self.new_args.extend(args[i:])
def starstarkwargs(self, p, i, args, kwargs):
self.new_kw.update(kwargs)
def trim(self, params, args, kwargs):
for i, p in enumerate(params.values()):
if i: # skip first (self or cls) arg of unbound function
self.dispatch[p.kind](p, i - 1, args, kwargs)
return self.new_args, self.new_kw
class IgnoreExtraArgsMeta(type):
def __call__(cls, *args, **kwargs):
if cls.__new__ is not object.__new__:
func = cls.__new__
else:
func = getattr(cls, '__init__', None)
if func is not None:
sig = signature(func)
args, kwargs = _ArgTrimmer().trim(sig.parameters, args, kwargs)
return super().__call__(*args, **kwargs)
此元类适用于任何 Python 类,但如果您要以内置类型进行子类化,则 @987654372@ 或 __init__ 方法可能无法自省。这里不是这种情况,但是如果您要在其他情况下使用上述元类,您需要知道一个警告。
然后在你的数据类中使用上面的 metaclass 参数:
from dataclasses import dataclass
from typing import Union
@dataclass
class Employee(metaclass=IgnoreExtraArgsMeta):
name: str
lastname: str
age: Union[int, None]
salary: int
department: str
def __post_init__(self):
try:
self.age = int(self.age)
except (ValueError, TypeError):
# could not convert age to an integer
self.age = None
def __str__(self):
return f'{self.name}, {self.lastname}, {self.age}'
使用元类的优势在这里应该很清楚;无需重复__init__ 方法中的所有字段。
第一种方法的演示:
>>> from dataclasses import dataclass
>>> from typing import Union
>>> @dataclass
... class Employee(object):
... name: str
... lastname: str
... age: Union[int, None] # set to None if conversion fails
... salary: int
... department: str
... def __init__(self,
... name: str,
... lastname: str,
... age: Union[int, None],
... salary: int,
... department: str,
... *args: Any,
... **kwargs: Any,
... ) -> None:
... self.name = name
... self.lastname = lastname
... try:
... self.age = int(age)
... except (ValueError, TypeError):
... # could not convert age to an integer
... self.age = None
... self.salary = salary
... self.department = department
... def __str__(self):
... return f'{self.name}, {self.lastname}, {self.age}'
...
>>> dic = {"name":"abdülmutallip",
... "lastname":"uzunkavakağacıaltındauzanıroğlu",
... "age":"24", "salary":2000, "department":"İK",
... "city":"istanbul", "country":"tr", "adres":"yok", "phone":"0033333"}
>>> a = Employee(**dic)
>>> a
Employee(name='abdülmutallip', lastname='uzunkavakağacıaltındauzanıroğlu', age=24, salary=2000, department='İK')
>>> print(a)
abdülmutallip, uzunkavakağacıaltındauzanıroğlu, 24
>>> a.age
24
>>> Employee(name="Eric", lastname="Idle", age="too old to tell", salary=123456, department="Silly Walks")
Employee(name='Eric', lastname='Idle', age=None, salary=123456, department='Silly Walks')
第二种方法:
>>> @dataclass
... class Employee(metaclass=IgnoreExtraArgsMeta):
... name: str
... lastname: str
... age: Union[int, None]
... salary: int
... department: str
... def __post_init__(self):
... try:
... self.age = int(self.age)
... except (ValueError, TypeError):
... # could not convert age to an integer
... self.age = None
... def __str__(self):
... return f'{self.name}, {self.lastname}, {self.age}'
...
>>> a = Employee(**dic)
>>> print(a)
abdülmutallip, uzunkavakağacıaltındauzanıroğlu, 24
>>> a
Employee(name='abdülmutallip', lastname='uzunkavakağacıaltındauzanıroğlu', age=24, salary=2000, department='İK')
>>> Employee("Michael", "Palin", "annoyed you asked", salary=42, department="Complaints", notes="Civil servants should never be asked for their salary, either")
Employee(name='Michael', lastname='Palin', age=None, salary=42, department='Complaints')
如果age 是可选的(因此,具有默认值),则将其移动到字段的末尾,将其指定为Optional[int] 作为类型,并将None 分配给它。您必须在自己指定的 __init__ 方法中执行相同的操作:
from typing import Optional
@dataclass
class Employee(object):
name: str
lastname: str
age: Optional[int] = None
salary: int
department: str
def __init__(
self,
name: str,
lastname: str,
salary: int,
department: str,
age: Optional[int] = None,
*args: Any,
**kwargs: Any,
) -> None:
# ...