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
的元素具有的唯一属性是他们是 Callable
和 Hashable
。有没有办法表明数据 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
。
我想编写一个 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
的元素具有的唯一属性是他们是 Callable
和 Hashable
。有没有办法表明数据 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
。