如何注释函数生成数据类?
假设您想像这样包装 dataclass
装饰器:
from dataclasses import dataclass
def something_else(klass):
return klass
def my_dataclass(klass):
return something_else(dataclass(klass))
How should my_dataclass
和/或 something_else
注释以指示返回类型是数据类? 请参阅以下示例,了解内置 @dataclass
如何工作,但自定义 @my_dataclass
则不然:
@dataclass
class TestA:
a: int
b: str
TestA(0, "") # fine
@my_dataclass
class TestB:
a: int
b: str
TestB(0, "") # error: Too many arguments for "TestB" (from mypy)
Say you want to wrap the dataclass
decorator like so:
from dataclasses import dataclass
def something_else(klass):
return klass
def my_dataclass(klass):
return something_else(dataclass(klass))
How should my_dataclass
and/or something_else
be annotated to indicate that the return type is a dataclass?
See the following example on how the builtin @dataclass
works but a custom @my_dataclass
does not:
@dataclass
class TestA:
a: int
b: str
TestA(0, "") # fine
@my_dataclass
class TestB:
a: int
b: str
TestB(0, "") # error: Too many arguments for "TestB" (from mypy)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
在 PEP 681 之前,没有可行的方法可以做到这一点。
dataclass
不描述类型,而是描述转换。其实际效果无法用 Python 的类型系统来表达 -@dataclass
由 MyPy 插件 用于检查代码,而不仅仅是类型。这是在特定装饰器上触发而不了解它们的实现。虽然可以提供自定义 MyPy 插件,但这通常超出了大多数项目的范围。 PEP 681 (Python 3.11) 添加了一个通用的“此装饰器的行为类似于
@dataclass
”标记,可用于从注释到字段的所有转换器。PEP 681 可通过
typing_extensions
适用于早期的 Python 版本。强制数据类
对于纯类型替代方案,定义自定义装饰器来获取数据类并修改它。数据类可以通过其
__dataclass_fields__
字段来标识。这使得类型检查器能够理解并验证是否需要
dataclass
类。自定义类似数据类的装饰器
PEP 681
dataclass_transform
装饰器是其他装饰器的标记,用于显示它们的行为“类似于”@dataclass
。为了匹配@dataclass的行为,必须使用field_specifiers来指示字段以相同的方式表示。自定义数据类装饰器可以将所有关键字视为
@dataclass
。dataclass_transform
可用于标记它们各自的默认值,即使装饰器本身不接受作为关键字也是如此。There is no feasible way to do this prior to PEP 681.
A
dataclass
does not describe a type but a transformation. The actual effects of this cannot be expressed by Python's type system –@dataclass
is handled by a MyPy Plugin which inspects the code, not just the types. This is triggered on specific decorators without understanding their implementation.While it is possible to provide custom MyPy plugins, this is generally out of scope for most projects. PEP 681 (Python 3.11) adds a generic "this decorator behaves like
@dataclass
"-marker that can be used for all transformers from annotations to fields.PEP 681 is available to earlier Python versions via
typing_extensions
.Enforcing dataclasses
For a pure typing alternative, define your custom decorator to take a dataclass and modify it. A dataclass can be identified by its
__dataclass_fields__
field.This allows the type checker to understand and verify that a
dataclass
class is needed.Custom dataclass-like decorators
The PEP 681
dataclass_transform
decorator is a marker for other decorators to show that they act "like"@dataclass
. In order to match the behaviour of@dataclass
, one has to usefield_specifiers
to indicate that fields are denoted the same way.It is possible for the custom dataclass decorator to take all keywords as
@dataclass
.dataclass_transform
can be used to mark their respective defaults, even when not accepted as keywords by the decorator itself.问题是 mypy 理解元类装饰器及其魔力
关于
__init__
,但不理解dataclass函数
:如您所见,
Test2
的__init__
方法确实不接受任何论据。The problem is that mypy understands the
metaclass decorator
and its magicabout
__init__
, but does not understand thedataclass function
:As you can see, the
__init__
method ofTest2
does not accept any arguments.当前接受的答案和评论描述了当前存在问题的原因。
存在以下大多数独立的问题:
1 & 2 由
dataclass
和dataclass_transform
装饰器完成,或者更确切地说,它们是由类型检查器处理的。然而,对于人类来说,这在类型提示或注释中不可见,他们需要查看装饰器。虽然它们不是注释,但它们解决了原始问题的问题。
2& 3 仅隐式可以通过 typeshed
is_dataclass(obj) -> TypeIs[DataclassInstance]
形成条件交集类型。4 对于人类来说只能通过
Annotated
或TypeAliasType
完成(请参阅此答案的底部)。下面我将回答有关如何显式注释此类函数或对象以及存在哪些问题的问题。
如果 Python 中的显式 Intersection 类型可以在 DataclassInstance 协议(请参阅本文结尾)和泛型之间形成子类:
(type[T] )->类型[DataclassInstance & T]
。目前最好的可能是通过类型检查器的条件隐式交集,它受
T
限制,因此非常有限:类型检查器可以使用的上述类型保护是 python 的 typeshed 包提供的重载。
typeshed 是大多数类型检查器使用的官方库,它提供超出正常类型提示的类型提示带注释的模块的数量。
_typeshed
类型提示在运行时不可用,必须适当保护,例如if TYPE_CHECKING
,通过将类型提示封装在字符串,或使用from __future__ import 注解
。虽然它看起来很简单并且您实际上可以做到这一点,但不鼓励在实际代码中包含
_typeshed
,它适用于存根文件(.pyi),即如果您使用它你应该为你的函数编写一个存根文件。您的 IDE 类型检查器可能已经可以使用它,但如果需要,您可以通过以下方式安装它:
注意:以下代码显示它可以显式完成,但它不是这是一个完美的解决方案,因为它不能以通用方式毫无问题地完成。
不起作用的是:
为什么最后的语句会失败?这是因为类型检查器不会将 Foo 视为 Foo & 的子类。数据类实例。目前这样的类型保护或特殊情况还不存在。对于
@dataclass
,这取决于类型检查器如何解释该类:_typeshed.DataclassInstance
正如已经回答的,您可以使用 Protocol 和
_typeshed.DataclassInstance
实际上是一个。目前无法将对象显式注释为
DataclassInstance
并同时保留其原始类。通用 TypeAliasType 注释
对类型检查器没有任何好处,但人类的通用注释提供了以下代码:
The currently accepted answer and comments describe why this is currently problematic.
There are the following problems, that are mostly independent:
__init__
signature1 & 2 are done by the
dataclass
anddataclass_transform
decorator, or rather how they are handled by the type-checkers. However for a human this is not visible in type-hints or annotations, they would need to see the decorator.While they are not annotations they solve the problem of the original question.
2 & 3 implicitly only can be done by with the overloaded type-guard from typeshed
is_dataclass(obj) -> TypeIs[DataclassInstance]
to form an conditional intersection type.4 for humans only can be done via
Annotated
orTypeAliasType
(see bottom of this answer).In the following I answer the question about how to annotate such a function or object explicitly and what problems there are.
It could be done if there were the possibility for an explicit Intersection type in Python to form a subclass between the
DataclassInstance
Protocol (see end of this post) and a generic:(type[T]) -> type[DataclassInstance & T]
.Currently the best that is possibly is a conditional implicit intersection through type-checkers which is bounded by
T
and therefore very limited:The mentioned type-guard that type-checkers can use is an overload provided by python's typeshed package.
typeshed is the official library used by most type-checkers which provides type-hints beyond the normal type-hints of the modules that are annotated.
_typeshed
type-hints are not available at runtime and have to be guarded appropriately, e.g.if TYPE_CHECKING
, forward-references by encapsulating type-hints in strings, or usingfrom __future__ import annotations
.While it looks simple and you can actually do it, it is not encouraged to include
_typeshed
in real code and it is intended for stub files (.pyi), i.e. if you use it you should write a stub file for your function.Your IDE type-checker might already can work with it but if needed you can install it via:
NOTE: The following code shows that it can be done explicitly, but its not a perfect solution as it cannot be done in a generic way without problems.
What does not work is:
Why do the last statements fail? It is because type-checkers do not see
Foo
as a subclass ofFoo & DataclassInstance
. Currently such a type-guard or special case does not yet exist. For@dataclass
it depends how the type-checker interprets the class:The
_typeshed.DataclassInstance
As already answered you can use a Protocol and
_typeshed.DataclassInstance
is in fact one.It is currently not possible to annotate objects explicitly as a
DataclassInstance
and keep their original class at the same time.Generic TypeAliasType Annotation
Without any great benefit for the type-checker but a generic annotation for human provides the following code:
现有的答案似乎没有太多关于如何使用 dataclass_transform() 的信息,因此这里有一个实用指南。
@dataclass_transform
是一个装饰器,用于另一个装饰器函数、类或元类。这些装饰符号将成为“数据类工厂”。基本用法
(游乐场:我的py,Pyright)
(游乐场:Mypy, Pyright)
(游乐场:Mypy, Pyright)
@dataclass_transform
的参数根据工厂的行为方式,您可以提供 相应的关键字参数
@dataclass_transform
:(游乐场:Mypy, Pyright)
字段和字段说明符
@dataclass_transform
还有一个名为field_specifiers
的参数。赋予此参数的参数必须是可调用元组,每个可调用元组都可用于描述分配给它的字段的行为。下面是一个如何使用
dataclasses.field
的小示例:(playgrounds: Mypy, Pyright)
以下是如何使用自定义装饰器和字段说明符执行相同操作:
(playgrounds: Mypy, Pyright)
可以找到字段说明符参数列表 规范中。
一个小警告:
(游乐场:Mypy, Pyright)
Existing answers don't seem to have much information on how to use
dataclass_transform()
, so here's a practical guide.@dataclass_transform
is a decorator, meant to be used on another decorator function, a class, or a metaclass. These decorated symbols will then become "dataclass factories".Basic usages
(playgrounds: Mypy, Pyright)
(playgrounds: Mypy, Pyright)
(playgrounds: Mypy, Pyright)
@dataclass_transform
's parametersDepending on how the factory behaves, you can provide corresponding keyword arguments to
@dataclass_transform
:(playgrounds: Mypy, Pyright)
Fields and field specifiers
@dataclass_transform
also has a parameter namedfield_specifiers
. The argument given to this parameter must be a tuple of callables, each of which can be used to describe the behaviour of the field it is assigned to.Here's a small example of how
dataclasses.field
can be used:(playgrounds: Mypy, Pyright)
And here's how to do the same with a custom decorator and field specifier(s):
(playgrounds: Mypy, Pyright)
A list of field specifier parameters can be found in the spec.
A small caveat:
(playgrounds: Mypy, Pyright)