【问题标题】:Python type hints for a hashable, callable dataclass可散列、可调用数据类的 Python 类型提示
【发布时间】:2021-07-10 01:29:54
【问题描述】:

我想编写一个 Python 函数,它将 Callable 对象和相应的参数作为输入,并返回从 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]})

但是,我正在努力让它与更广泛的 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 .有没有办法表明数据类满足MyFunctionInterface,并且__hash__ 函数仍然由dataclass 装饰器生成?

【问题讨论】:

    标签: python python-typing python-dataclasses


    【解决方案1】:

    docs中所述

    默认情况下,dataclass() 不会隐式添加 __hash__() 方法,除非这样做是安全的。它也不会添加或更改现有的明确定义的__hash__() 方法。设置类属性__hash__ = None 对Python 有特定的含义,如__hash__() 文档中所述。

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

    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
    

    【讨论】:

    • Hashable 没有定义 __hash__dataclass 装饰器将 __hash__ 添加到类中就好了,但是对于 abc 来说太晚了。
    • MyFunctionInterfaceHashed 只是隐藏了问题——使用MyFunctionInterfaceHashed,即使子类根本没有实现__hash__,也可以实例化它们。 Here's an example.
    【解决方案2】:

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

    引用abc docs

    不支持向类动态添加抽象方法,或者在创建方法或类后尝试修改其抽象状态。

    一旦创建了一个类,就不能更改它的抽象性。不幸的是,像dataclasses.dataclass 这样的类装饰器会在类已经创建之后才起作用。

    最初创建 Adder 时,它没有 __hash__ 实现。 abc 此时检查类并确定该类是抽象的。然后,装饰器将__hash__ 和所有其他数据类内容添加到类中,但已经太迟了。

    您的类确实有一个__hash__ 方法,但abc 机制不知道这一点。


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

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

    然后将您的对象注释为MyProto

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-11-30
      • 2013-03-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-22
      • 2013-01-23
      相关资源
      最近更新 更多