如何指定可调用python数据类成员的类型以接受子类实例



我希望能够指定一个可调用的数据类成员,并采用派生类的实例。如果我使用抽象的成员函数,这将很容易。

我尝试过如下代码,但不知道为Callable类型规范提供什么参数。我正在寻找一种方法来注释handler属性,使派生类可以将自己的实例作为handler(在这种情况下为Specific)的参数,而不仅仅是Base

@dc.dataclass
class Base():
name: str
handler: typing.Callable[['Base'], str] # <--- What to use instead of 'Base'

@dc.dataclass
class Specific(Base):
specific: str

# This is the callable that I would like to use.
def specific_handler(v: Specific) -> str:
return f"{v.specific}"
# Assigning specific_handler to handler gives a type error.
sg = Specific(name="two", handler=specific_handler, specific="extra info")
# The handler can be used in following manner.
assert("extra info" == sg.handler(sg))

我正在使用Python 3.7.

虽然@dROOOze的答案有效,但它是不必要的冗长(需要重复类名作为通用属性)。下面的替代解决方案使用typing.Self(通过PEP673在python 3.11上添加,通过typing_extensions反向移植,在mypy主上支持-尽管0.991仍然缺乏此功能)。因此,如果您可以使用mypy主分支或稍后阅读此答案,则mypy 1.0及以上完全支持Self

这是一个操场。

import sys
from dataclasses import dataclass
from typing import Callable
# You can omit this guard for specific target version
if sys.version_info < (3, 11):
from typing_extensions import Self
else:
from typing import Self

@dataclass
class Base:
name: str
handler: Callable[[Self], str]
@dataclass
class Specific(Base):
specific: str
def specific_handler(v: Specific) -> str:
return f"{v.specific}"

sg = Specific(name="two", handler=specific_handler, specific="extra info")
assert "extra info" == sg.handler(sg)

解决方案仍然涉及typing.TypeVar

只要handler的第一个参数不接受与所属类相同类型的参数,那么Base必须是泛型的(如果是相同类型的,则可以使用typing.Self)。如果handler的第一个参数是Base的子类,这并不重要,这只是你添加到typing.TypeVar(bound=...)的一个细节。

import dataclasses as dc
import typing
T = typing.TypeVar("T", bound="Base[typing.Any]")
@dc.dataclass
class Base(typing.Generic[T]):
name: str
handler: typing.Callable[[T], str]
# `Base["Specific"]` assumes that you want `Specific.handler` to be of type `typing.Callable[[Specific], str]`, which is true in this situation.
# Otherwise just provide another subclass of `Base` (or `typing.Any`).
@dc.dataclass
class Specific(Base["Specific"]):
specific: str
def specific_handler(v: Specific) -> str:
return f"{v.specific}"
sg = Specific(name="two", handler=specific_handler, specific="extra info")
assert "extra info" == sg.handler(sg)

最新更新