簡體   English   中英

如何在 Python 中修復數據類的 TypeError?

[英]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.Optionalage字段的類型正確標記為可選。 Optional[int]等價於Union[int, None] 當沒有設置默認值並且省略age是不可接受的時,我個人更喜歡構造函數中的后者。
  • 使用isinstance()確定對象是否為字符串。 或者只是不要 test ,因為int(self.age)只是返回self.age如果它已經被設置為一個整數不變。
  • 如果可以將設置為0的年齡設置為or None ,則僅在__post_init__方法中使用or None None
  • 如果age是設置為Noneint(age)失敗,那么你必須使用try:...except處理ValueErrorTypeError例外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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM