[英]How can I fix the TypeError of my dataclass in Python?
我有一個具有 5 個屬性的數據類。 當我通過字典給出這些屬性時,效果很好。 但是當字典的屬性多於類的屬性時,類會給出 TypeError。 我試圖在有額外值時做到這一點,班級不會關心它們。 我怎樣才能做到這一點?
from dataclasses import dataclass
@dataclass
class Employee(object):
name: str
lastname: str
age: int or None
salary: int
department: str
def __new__(cls, name, lastname, age, salary, department):
return object.__new__(cls)
def __post_init__(self):
if type(self.age) == str:
self.age = int(self.age) or None
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)
print(a)
錯誤是:
TypeError: __new__() got an unexpected keyword argument 'city'
我希望課程在這種情況下正常工作,沒有任何錯誤。 我不想將這些額外的屬性添加到類中。
如果您希望數據類接受任意額外的關鍵字參數,那么您必須定義自己的__init__
方法,或者在元類上提供自定義__call__
方法。 如果你定義了一個自定義的__init__
方法, dataclass
裝飾器不會為你生成一個; 此時無需再使用__post_init__
,因為您已經在編寫__init__
方法。
旁注:
__new__
不能改變傳遞給__init__
參數。 元類的__call__
通常首先調用cls.__new__(<arguments>)
然后調用instance.__init__(<arguments>
__new__
的instance
返回值,請參閱數據模型文檔。int or None
,這是一個只返回int
的表達式,它不會讓您省略age
參數。 給該字段一個默認值,或者如果None
僅用於指示 age=0 或失敗的int()
轉換,則使用Union
類型提示。age
放在最后。age
是可選字段,則使用typing.Optional
將age
字段的類型正確標記為可選。 Optional[int]
等價於Union[int, None]
; 當沒有設置默認值並且省略age
是不可接受的時,我個人更喜歡構造函數中的后者。isinstance()
確定對象是否為字符串。 或者只是不要 test ,因為int(self.age)
只是返回self.age
如果它已經被設置為一個整數不變。0
的年齡設置為or None
,則僅在__post_init__
方法中使用or None
None
。age
是設置為None
僅int(age)
失敗,那么你必須使用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__
方法調用簽名來創建一個忽略幾乎任何類的所有額外參數的方法:
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 類,但如果您要在內置類型中進行子類化,則__new__
或__init__
方法可能無法自省。 不是這里的情況,但需要注意的是,如果要在其他情況下使用上述元類,則需要了解。
然后將上述內容用作數據metaclass
上的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:
# ...
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.