在python数据类__init__方法中强制进行类型转换

joh*_*son 7 python python-dataclasses

我有以下非常简单的数据类:

import dataclasses

@dataclasses.dataclass
class Test:
    value: int
Run Code Online (Sandbox Code Playgroud)

我创建了该类的实例,但我使用了一个字符串而不是整数:

>>> test = Test('1')
>>> type(test.value)
<class 'str'>
Run Code Online (Sandbox Code Playgroud)

我真正想要的是强制转换为我在类定义中定义的数据类型:

>>> test = Test('1')
>>> type(test.value)
<class 'int'>
Run Code Online (Sandbox Code Playgroud)

我必须__init__手动编写该方法还是有一种简单的方法来实现此目的?

Mer*_*rig 15

您可以使用以下__post_init__方法来实现此目的:

import dataclasses

@dataclasses.dataclass
class Test:
    value : int

    def __post_init__(self):
        self.value = int(self.value)
Run Code Online (Sandbox Code Playgroud)

该方法被调用如下__init__方法

https://docs.python.org/3/library/dataclasses.html#post-init-processing


dec*_*eze 8

从强制或检查类型的意义上讲,永远不要遵循dataclass属性的类型提示。大多数情况下,像mypy这样的静态类型检查器都可以完成这项工作,Python在运行时不会这样做,因为它永远不会这样做。

如果要添加手动类型检查代码,请使用以下__post_init__方法:

@dataclasses.dataclass
class Test:
    value: int

    def __post_init__(self):
        if not isinstance(self.value, int):
            raise ValueError('value not an int')
            # or self.value = int(self.value)
Run Code Online (Sandbox Code Playgroud)

您可以dataclasses.fields(self)用来获取Field指定字段和类型的对象的元组,并在其上循环以针对每个字段自动执行此操作,而无需为每个字段单独编写。

def __post_init__(self):
    for field in dataclasses.fields(self):
        value = getattr(self, field.name)
        if not isinstance(value, field.type):
            raise ValueError(f'Expected {field.name} to be {field.type}, '
                             f'got {repr(value)}')
            # or setattr(self, field.name, field.type(value))
Run Code Online (Sandbox Code Playgroud)

  • 谢谢!这不完全是我想要的(例外而不是转换),但是由于您的建议,我能够找到解决方案 (2认同)
  • 我会错误地选择异常而不是隐式转换,但我确实在评论中为您提供了转换替代方案...... (2认同)
  • 另一种[解决方案](/sf/answers/3543585041/) 使用装饰器代替`__post_init__`。 (2认同)

eri*_*cbn 8

对于 Python dataclasses,替代方法是使用该__post_init__方法,如其他答案中指出的:

@dataclasses.dataclass
class Test:
    value: int

    def __post_init__(self):
        self.value = int(self.value)
Run Code Online (Sandbox Code Playgroud)
>>> test = Test("42")
>>> type(test.value)
<class 'int'>
Run Code Online (Sandbox Code Playgroud)

或者您可以使用该attrs包,它可以让您轻松设置转换器

@attr.define
class Test:
    value: int = attr.field(converter=int)
Run Code Online (Sandbox Code Playgroud)
>>> test = Test("42")
>>> type(test.value)
<class 'int'>
Run Code Online (Sandbox Code Playgroud)

如果您的数据来自映射,您可以使用该包,它根据类和数据类cattrs中的类型注释进行转换:attr

@dataclasses.dataclass
class Test:
    value: int
Run Code Online (Sandbox Code Playgroud)
>>> test = cattrs.structure({"value": "42"}, Test)
>>> type(test.value)
<class 'int'>
Run Code Online (Sandbox Code Playgroud)

Pydantic将根据模型中字段的类型自动进行转换:

class Test(pydantic.BaseModel):
    value: int
Run Code Online (Sandbox Code Playgroud)
>>> test = Test(value="42")
>>> type(test.value)
<class 'int'>
Run Code Online (Sandbox Code Playgroud)


小智 5

使用pydantic.validate_arguments很容易实现

只需validate_arguments在您的数据类中使用装饰器:

from dataclasses import dataclass
from pydantic import validate_arguments


@validate_arguments
@dataclass
class Test:
    value: int

Run Code Online (Sandbox Code Playgroud)

然后试试你的演示,'str type' 1 将从 转换strint

>>> test = Test('1')
>>> type(test.value)
<class 'int'>
Run Code Online (Sandbox Code Playgroud)

如果你传递了真正错误的类型,它会引发异常

>>> test = Test('apple')
Traceback (most recent call last):
...
pydantic.error_wrappers.ValidationError: 1 validation error for Test
value
  value is not a valid integer (type=type_error.integer)
Run Code Online (Sandbox Code Playgroud)