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 call- cls.__new__(<arguments>)then call- instance.__init__(<arguments>on the- instancereturn 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?"