How Can I Fix The Typeerror Of My Dataclass In Python?
Solution 1:
If you want the dataclass to accept arbitrary extra keyword arguments then you either have to define your own __init__ method, or provide a custom __call__ method on a metaclass. If you define a custom __init__ method, the dataclass decorator won't generate one for you; at this point there is no need to use __post_init__ any more either since you already are writing an __init__ method.
Side notes:
__new__can't alter what arguments are passed to__init__. The metaclass's__call__will normally first callcls.__new__(<arguments>)then callinstance.__init__(<arguments>on theinstancereturn value from__new__, see the datamodel documentation.- You can't use
int or None, that's an expression that just returnsint, it won't let you omit theageparameter. Give the field a default value instead, or use aUniontype hint ifNoneis only used to indicate age=0 or a failedint()conversion. - Fields that have a default defined must come after fields that do not have a default defined, so put
ageat the end. - If you also use type hinting beyond dataclasses, and
ageis meant to be an optional field, then usetyping.Optionalto properly mark the type of theagefield as optional.Optional[int]is equivalent toUnion[int, None]; personally I prefer the latter in constructors when there is no default value set and omittingageis not acceptable. - Use
isinstance()to determine if an object is a string. Or just don't test, sinceint(self.age)just returnsself.ageunchanged if it already is set to an integer. - Only use
or Nonein the__post_init__method if it is okay for an age set to0to be set toNone. - If
ageis to be set toNoneonly ifint(age)fails, then you have to usetry:...exceptto handle theValueErrororTypeErrorexceptions thatint()can raise in that case, notor None.
Assuming that you meant for age to be set to None only if conversion fails:
from dataclasses import dataclass
from typing import Union@dataclass
class Employee(object):
name: str
lastname: str
age: Union[int, None] # settoNone 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 notconvert age to an integer
self.age =None
self.salary = salary
self.department = department
def __str__(self):
return f'{self.name}, {self.lastname}, {self.age}'If you want to go the metaclass route, then you can create one that ignores all extra arguments for almost any class, by introspecting the __init__ or __new__ method call signature:
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,
}
defpos_only(self, p, i, args, kwargs):
if i < len(args):
self.new_args.append(args[i])
defkw_only(self, p, i, args, kwargs):
if p.name in kwargs:
self.new_kw[p.name] = kwargs.pop(p.name)
defpos_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]
defstarargs(self, p, i, args, kwargs):
self.new_args.extend(args[i:])
defstarstarkwargs(self, p, i, args, kwargs):
self.new_kw.update(kwargs)
deftrim(self, params, args, kwargs):
for i, p inenumerate(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
classIgnoreExtraArgsMeta(type):
def__call__(cls, *args, **kwargs):
if cls.__new__ isnotobject.__new__:
func = cls.__new__
else:
func = getattr(cls, '__init__', None)
if func isnotNone:
sig = signature(func)
args, kwargs = _ArgTrimmer().trim(sig.parameters, args, kwargs)
returnsuper().__call__(*args, **kwargs)
This metaclass will work for any Python class, but if you were to subclass in a built-in type then the __new__ or __init__ methods may not be introspectable. Not the case here, but a caveat that you would need to know about if you were to use the above metaclass in other situations.
Then use the above as a metaclass parameter on your dataclass:
from dataclasses import dataclassfrom typing importUnion@dataclassclassEmployee(metaclass=IgnoreExtraArgsMeta):
name: str
lastname: str
age: Union[int, None]
salary: int
department: strdef__post_init__(self):
try:
self.age = int(self.age)
except (ValueError, TypeError):
# could not convert age to an integer
self.age = Nonedef__str__(self):
returnf'{self.name}, {self.lastname}, {self.age}'The advantage of using a metaclass should be clear here; no need to repeat all the fields in the __init__ method.
Demo of the first approach:
>>>from dataclasses import dataclass>>>from typing importUnion>>>@dataclass...classEmployee(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):...returnf'{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')
and of the second approach:
>>>@dataclass...classEmployee(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):...returnf'{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')
If age is meant to be optional (so, have a default value), then move it to the end of the fields, give it Optional[int] as the type, and assign None to it. You'll have to do the same in the __init__ method you specify your own:
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:
# ...
Post a Comment for "How Can I Fix The Typeerror Of My Dataclass In Python?"