python 泛型类联合Callable的类型提示

7vhp5slm  于 2023-04-19  发布在  Python
关注(0)|答案(2)|浏览(130)

给出:

from typing import Callable, Generic, TypeVar

T = TypeVar("T")
class Factory(Generic[T]):
    def __call__(self) -> T:
        ...

class TruthyFactory(Factory[bool]):
    def __call__(self) -> bool:
        return True

def falsey_factory() -> bool:
    return False

def class_consumer(factory: type[Factory[T]]) -> T:
    ...

def function_consumer(factory: Callable[[], T]) -> T:
    ...

# type hint for cls_ret is `bool`
cls_ret = class_consumer(TruthyFactory)

# type hint for fn_ret is `bool`
fn_ret = function_consumer(falsey_factory)

对于一个同时使用两种参数类型的函数,签名(类型提示)是什么样子的?
也就是说,函数签名为:def either_consumer(factory: ???) -> T: ...
我尝试使用type[Factory[T]] | Callable[[], T],但它不适用于子类Factory[T]的类型。返回类型提示成为类的类型。我相信这是因为类匹配Callable规范-这是否具有更高的亲和力,是否有方法修改这种行为?

def either_consumer(factory: type[Factory[T]] | Callable[[], T]) -> T:
    # needs more stringent boolean logic
    if isinstance(factory, type):
        return factory()()
    return factory()

# type hint for cls_ret is wrongly `TruthyFactory`
cls_ret = either_consumer(TruthyFactory)

# type hint for fn_ret is correctly `bool`
fn_ret = either_consumer(falsey_factory)
vuktfyat

vuktfyat1#

我不确定我是否真的建议这样做,但是考虑到Factory是一个不带参数的可调用对象,它返回一个不带参数的可调用对象,并返回一个T

Callable[[], Callable[[], T]] | Callable[[], T]

参数类型。一个小的类型代数可以让你重构为

Callable[[], Callable[[], T] | T]

当你调用factory时,你要么得到了想要的boolean值,要么得到了一个可调用对象,然后再次调用它来得到一个boolean值。

def either_consumer(factory: Callable[[], Callable[[], T] | T]) -> T:
    rv = factory()
    if isinstance(rv, bool):
        return rv
    return rv()

我更倾向于让consumer严格地接受Callable[[], T],并让 caller 负责示例化一个工厂类,以获得一个类型为Callable[[], T]的对象。

FactoryType = Callable[[], T]

def consumer(f: FactoryType[T]) -> T
    ...

consumer(TruthyFactory())
consumer(falsey_factory)
hujrc8aj

hujrc8aj2#

好吧,我找到了一个答案(至少在vscode中使用了pyright)。
将参数类型设置为Callable[[], T] | type[Factory[T]]可以工作,但type[Factory[T]] | Callable[[], T]不能。
type[Callable[[], T] | Factory[T]]也可以。
我更喜欢第二种选择,但我不知道这是否完全正确,或者它只是碰巧起作用的未定义行为。我不能规范地说为什么......也许函数引用的“类型”是根据类型系统的 * 签名 *?这些参数的顺序很重要,我还假设类型系统匹配联合中的最后一个匹配类型?
第一个选项只适用于pyright。第二个选项适用于pyrightjetbrains'。我没有测试过mypy
完整示例:

from typing import Callable, Generic, TypeVar

T = TypeVar("T")
class Factory(Generic[T]):
    def __call__(self) -> T:
        ...

class TruthyFactory(Factory[bool]):
    def __call__(self) -> bool:
        return True

def falsey_factory() -> bool:
    return False

def either_consumer(factory: type[Callable[[], T] | Factory[T]]) -> T:
    # needs more stringent boolean logic
    if isinstance(factory, type):
        return factory()()
    return factory()

# type hint for cls_ret is correctly `bool`
cls_ret = either_consumer(TruthyFactory)

# type hint for fn_ret is correctly `bool`
fn_ret = either_consumer(falsey_factory)

重新排列@chepner的答案也有效,但不受限制Callable[[], T | Callable[[], T]]
在更多的实验中,这似乎完全不一致。

相关问题