如何在数据类中一起使用 match_args 和 kw_args ?

Dos*_*sod 5 python python-dataclasses python-3.10 structural-pattern-matching

给出以下示例:

from dataclasses import dataclass

@dataclass
class Person:
    name: str = ""


@dataclass(kw_only=True)
class AnotherPerson:
    name: str = ""


print(Person.__match_args__)
print(AnotherPerson.__match_args__)
Run Code Online (Sandbox Code Playgroud)

运行时,您会得到以下信息:

('name',)
()
Run Code Online (Sandbox Code Playgroud)

根据dataclass match_args参数的文档(重点是我的):

match_args:如果为 true(默认为 True),将从生成的 __init__() 方法的参数列表中创建 __match_args__ 元组(即使未生成 __init__(),请参见上文)。如果为 false,或者类中已定义 __match_args__,则不会生成 __match_args__。

鉴于match_args默认值为 true,我认为__match_args__变量应该设置为方法中出现的值__init__,尽管关键字参数的情况似乎并非如此。这只是一个未记录的限制,还是我做错了什么?

__match_args__无论如何,我将如何在不明确写出这些元组的情况下生成它们?

小智 0

这是一个老问题,但我偶然发现了它,所以我想我会回答(在 python 3.12 中测试),以防其他人也偶然发现它。

首先,我怀疑不包含仅关键字参数的原因__match_args__是因为您已经可以在匹配情况下使用关键字,并且仅在类中使用参数关键字而不是在匹配中使用参数关键字有点奇怪(见下文) 。因此,如果您需要该类仅是关键字并且以下内容适合您,那么它可能是最好的答案:

@dataclasses.dataclass(kw_only=True)
class A:
    a: int
    b: int

a = A(a=1, b=2)
match a:
    case A(a=1, b=_):
        print('Of type A with .a=1')
Run Code Online (Sandbox Code Playgroud)

但如果这还不够,您可以__match_args__使用以编程方式修改属性dataclasses.fields。如果你要经常这样做,你可以创建一个装饰器来为你做这件事 - 但你必须决定如何处理仅与非仅关键字和继承混合的关键字(如果适用)。例如:

def add_keyword_match_args(cls):
    # dataclasses.fields gives a tuple of objects
    # representing each field - we are interested
    # in the string name of the field and whether
    # or not it's keyword only. We can add these
    # to the end of __match_args__
    cls.__match_args__ += tuple(
        field.name
        for field in dataclasses.fields(cls)
        if field.kw_only
    )
    return cls

@add_keyword_match_args
@dataclasses.dataclass(kw_only=True)
class A:
    a: int = 3
    b: int = 4

@add_keyword_match_args
@dataclasses.dataclass
class B(A):
    c: int
    d: int

print(A.__match_args__)  # ('a', 'b')
print(B.__match_args__)  # ('c', 'd', 'a', 'b')

b = B(1, 2)
match b:
    case B(1, 2):
        print('Yay.')

match b:
    case B(1, 2, 3, 4):
        print('Yay.')

match b:
    case B(1, 2, a=3, b=4):
        print('Yay.')
Run Code Online (Sandbox Code Playgroud)

请注意,在我上面给出的确切示例中,关键字参数仅A位于B位置c和之后的元组中d。一般来说,数据类的东西将字段按顺序排列,以便基类的字段位于子类的字段之前 - 但如果我们这样做,那么 thenB.__match_args__ == ('a', 'b', 'c', 'd')B(1, 2)不会与 匹配B(1, 2),这似乎会令人困惑。

当然,事实上我们可以B(1, 2, 3, 4)在匹配的情况下执行,但不创建对象也可能有点令人困惑。事实上,对于匹配情况,基类的字段位于子类的字段之后,尽管在所有其他情况下情况并非如此。我认为这是不可避免的,仅删除关键字的性质A.aA.b仅适用于匹配情况。

因此,我可能会建议在任何情况下都尽可能在匹配情况下使用关键字参数进行匹配。但如果您需要以__match_args__编程方式进行修改,那么这是可能的。