9

我有一个 Python 类,它的字段可以传递几种序列类型之一。为了简化,我将坚持使用元组和列表。__init__将参数转换为MyList.

from typing import Union
from dataclasses import dataclass, InitVar, field

class MyList(list):
    pass

@dataclass
class Struct:
    field: Union[tuple, list, MyList]

    def __post_init__(self):
        self.field = MyList(self.field)

我应该使用什么类型的field声明?

  • 如果我提供所有可能输入类型的并集,则代码不会记录field始终为MyListwhen access的代码。
  • 如果我只提供最终MyList类型,PyCharm 会在我Struct()通过list.

我可以改为使用:

_field: InitVar[Union[tuple, list, MyList]] = None
field: MyList = field(init=False)

def __post_init__(self, _field):
    self.field = MyList(_field)

但这非常难看,尤其是在跨 3 个字段重复时。此外,我必须构造一个结构,Struct(_field=field)而不是Struct(field=field).

2018年4月,“tm”在PyCharm的公告上评论了这个问题:https ://blog.jetbrains.com/pycharm/2018/04/python-37-introducing-data-class/#comment-323957

4

3 回答 3

6

您将向属性分配值与生成要分配给属性的值的代码混为一谈。我会使用一个单独的类方法来将两段代码分开。

from dataclasses import dataclass


class MyList(list):
    pass


@dataclass
class Struct:
    field: MyList

    @classmethod
    def from_iterable(cls, x):
        return cls(MyList(x))


s1 = Struct(MyList([1,2,3]))
s2 = Struct.from_iterable((4,5,6))

现在,您传递一个现有值MyListto Struct.__init__。元组、列表和任何其他MyList可以接受的东西都被传递给Struct.from_iterable,这将负责构造MyList要传递给的实例Struct

于 2021-08-11T14:37:29.230 回答
0

您是否尝试过 Pydantic BaseModel 而不是数据类?

使用以下代码,我的 Pycharm 不会抱怨:

from pydantic import BaseModel


class MyList(list):
    pass


class PydanticStruct(BaseModel):
    field: MyList

    def __post_init__(self):
        self.field = MyList(self.field)


a = PydanticStruct(field=['a', 'b'])
于 2021-08-10T22:33:17.267 回答
0

dataclasses在直接的数据容器中效果最好,有意识地省略了转换等高级实用程序(有关此功能和类似功能的完整说明,请参见此处)。实现这一点是一项相当大的工作,因为它还应该包括 pycharm 插件,该插件会注意到现在支持多远的转换。

一个更好的方法是使用已经这样做的第 3 方之一,最流行的是pydantic,可能是因为它具有最简单的数据类迁移


本机pydantic解决方案可能如下所示,其中转换代码是MyList. 以这种方式处理它会使__post_init__不必要的,从而导致更清晰的模型定义:

import pydantic


class MyList(list):
    @classmethod
    def __get_validators__(cls):
        """Validators handle data validation, as well as data conversion.

        This function yields validator functions, with the last-yielded
        result being the final value of a pydantic field annotated with
        this class's type.
        Since we inherit from 'list', our constructor already supports
        building 'MyList' instances from iterables - if we didn't, we 
        would need to write that code by hand and yield it instead.
        """
        yield cls


class Struct(pydantic.BaseModel):
    field: MyList  # accepts any iterable as input


print(Struct(field=(1, 2, 3)))
# prints: field=[1, 2, 3]
于 2021-08-11T14:14:21.293 回答