简体   繁体   中英

How to make List in python dataclass that can accept multiple different types?

I am creating new data class in python.

@dataclass
class User(Mixin):
  id: int = None
  items: List[DefaultItem] = None

This items is array of DefaultItem objects but I need this to be multiple possible objects like:

items: List[DefaultItem OR SomeSpecificItem OR SomeOtherItem] = None

How can I do something like this in python?

You can use typing.Union for this.

items: List[Union[DefaultItem, SomeSpecificItem, SomeOtherItem]] = None

And if you are on Python 3.10, they've added a convenient shorthand notation:

items: list[DefaultItem | SomeSpecificItem | SomeOtherItem] = None

Also just as a note: If items is allowed to be None , you should mark the type as Optional .

Also, a note that in Python 3.10, you can also pass the kw_only parameter to the @dataclass decorator to work around the issue which I suspect you're having, wherein all fields in a subclass are required to have a default value when there is at least one field with a default value in the superclass, Mixin in this case.

I added an example below to illustrate this a little better:

from dataclasses import dataclass


@dataclass
class Mixin:
    string: str
    integer: int = 222


@dataclass(kw_only=True)
class User(Mixin):
    id: int
    items: list['A | B | C']


class A: ...
class B: ...
class C: ...


u = User(string='abc', id=321, integer=123, items=[])
print(u)

Note that I've also wrapped the Union arguments in a string, so that the expression is forward-declared (ie not evaluated yet), since the classes in the Union arguments are defined a bit later.

This code works in 3.10 because the kw_only param is enabled, so now only keyword arguments are accepted to the constructor. This allows you to work around that issue as mentioned, where you would otherwise need to define a default value for all fields in a subclass when there's at least one default field in a parent class.

In earlier Python versions than 3.10, missing the kw_only argument, you'd expect to run into a TypeError as below:

TypeError: non-default argument 'id' follows default argument

The workaround for this in a pre-3.10 scenario is exactly how you had it: define a default value for all fields in the User class as below.

from __future__ import annotations

from dataclasses import dataclass, field


@dataclass
class Mixin:
    string: str
    integer: int = 222


@dataclass
class User(Mixin):
    id: int = None
    items: list[A | B | C] = field(default_factory=list)


class A: ...
class B: ...
class C: ...


u = User('abc', 123, 321)
print(u)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM