Python 可哈希、可调用数据类的类型提示

Python type hints for a hashable, callable dataclass

我想编写一个 Python 函数,它将 Callable 对象和相应的参数作为输入,returns 从 Callable 对象到值的映射这些对象上的参数。更具体地说,代码可能如下所示。

>>> import collections
>>> import dataclasses
>>> from typing import Iterable, List, Mapping
>>> @dataclasses.dataclass(frozen=True)
... class Adder:
...     x: int = 0
...     def __call__(self, y: int) -> int:
...             return self.x + y
... 
>>> def fn_vals(fns: Iterable[Adder], vals: Iterable[int]) -> Mapping[Adder, List[int]]:
...     values_from_function = collections.defaultdict(list)
...     for fn in fns:
...             for val in vals:
...                     values_from_function[fn].append(fn(val))
...     return values_from_function
... 
>>> fn_vals((Adder(), Adder(2)), (1, 2, 3))
defaultdict(<class 'list'>, {Adder(x=0): [1, 2, 3], Adder(x=2): [3, 4, 5]})

但是,我正在努力让它与更广泛的 class Callable 个对象一起使用。特别是,以下失败并显示 __hash__ 尚未实现的错误。

>>> import dataclasses
>>> from typing import Callable, Hashable
>>> class MyFunctionInterface(Callable, Hashable): pass
... 
>>> @dataclasses.dataclass(frozen=True)
... class Adder(MyFunctionInterface):
...     x: int = 0
...     def __call__(self, y: int) -> int:
...             return self.x + y
... 
>>> Adder()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/alex/anaconda3/lib/python3.7/typing.py", line 814, in __new__
    obj = super().__new__(cls)
TypeError: Can't instantiate abstract class Adder with abstract methods __hash__

我想修改我的 fn_vals 函数,使 fns 具有类型 Iterable[MyFunctionInterface],因为我需要 fns 的元素具有的唯一属性是他们是 CallableHashable。有没有办法表明数据 class 满足 MyFunctionInterface,并且 __hash__ 函数仍然由 dataclass 装饰器生成?

docs

所述

By default, dataclass() will not implicitly add a __hash__() method unless it is safe to do so. Neither will it add or change an existing explicitly defined __hash__() method. Setting the class attribute __hash__ = None has a specific meaning to Python, as described in the __hash__() documentation.

看起来 Hashable 定义了 __hash__ 方法,因此数据 class 不会显式定义 __hash__。因此,创建新的 class 扩展 MyFunctionInterface 并设置 __has__ = None 并用它扩展加法器。

import dataclasses
from typing import Callable, Hashable
class MyFunctionInterface(Callable, Hashable): pass

class MyFunctionInterfaceHashed(MyFunctionInterface):
    __hash__ = None

@dataclasses.dataclass(frozen=True)
class Adder(MyFunctionInterfaceHashed):
    x: int = 0

    def __call__(self, y: int) -> int:
        return self.x + y

这里的问题是 abc 和 class 装饰器之间的不良交互。

引用 abc docs,

Dynamically adding abstract methods to a class, or attempting to modify the abstraction status of a method or class once it is created, are not supported.

一旦创建了 class,您将无法更改其抽象性。不幸的是,像 dataclasses.dataclass 这样的 class 装饰器会在 class 已经创建之后启动。

最初创建 Adder 时,它没有 __hash__ 实现。 abc 此时检查 class 并确定 class 是抽象的。然后,装饰器将 __hash__ 和所有其他数据 class 添加到 class,但为时已晚。

您的 class 有一个 __hash__ 方法,但是 abc 机制不知道。


至于如何进行,有两个主要选择。一种是完全消除 MyFunctionInterface,只将可调用哈希对象注释为 Any。第二个是,假设您希望您的对象可以使用单个 int 参数和 return 一个 int 专门调用,您可以定义一个协议

class MyProto(typing.Protocol):
    def __call__(self, y: int) -> int: ...
    def __hash__(self) -> int: ...

然后将您的对象注释为 MyProto