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 theinstance
return 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 theage
parameter. Give the field a default value instead, or use aUnion
type hint ifNone
is 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
age
at the end. - If you also use type hinting beyond dataclasses, and
age
is meant to be an optional field, then usetyping.Optional
to properly mark the type of theage
field as optional.Optional[int]
is equivalent toUnion[int, None]
; personally I prefer the latter in constructors when there is no default value set and omittingage
is not acceptable. - Use
isinstance()
to determine if an object is a string. Or just don't test, sinceint(self.age)
just returnsself.age
unchanged if it already is set to an integer. - Only use
or None
in the__post_init__
method if it is okay for an age set to0
to be set toNone
. - If
age
is to be set toNone
only ifint(age)
fails, then you have to usetry:...except
to handle theValueError
orTypeError
exceptions 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?"