相当于 Java 标记注释的 Pythonic

问题描述 投票:0回答:1

我想在运行时搜索我的 Python 类路径以查找可以处理特定输入(类型)的所有类。然后我将实例化它们并在收到输入时调用它们。我希望每个类作者都能够将他们的类标记为可能的处理程序。

在 Java 中,我可以定义注释

@Handler
并执行以下操作:

@Handler
public class MyHandler implements HandlerInterface {
    // whatever
}

然后使用 Reflections 库(或者 Spring Framework,如果我使用它)获取所有标记类的集合:

Set<Class<?>> handlerClasses = reflections.getTypesAnnotatedWith(Handler.class);

Python 的等价物是什么?

@decorator
似乎并没有实现完全相同的目标;它似乎完全适用于包装器,而这只是 Java 的
@
语法的一种可能用途。

我几乎可以用继承来做到这一点:

class MyHandler(HandlerInterface):
    pass

handlers = [
    handler() for handler in HandlerInterface.__subclasses__()
]

但是该解决方案可能会获得中间类(例如

HandlerInterface
->
HandlerType1
->
HandlerType1Implementation
)。

我还缺少其他选择吗?或者我应该考虑不同的设计模式?我可能是从过于以 Java 为中心的角度来看待它的。

python annotations
1个回答
0
投票

@decorator
似乎并没有实现完全相同的目标;它似乎完全适用于包装器,而这只是 Java
@
语法的一种可能用途。

这是不对的。在 Python 中,只要

@<arbitrary expression>
 计算结果为与以下接口兼容的实例,
<arbitrary expression>
就能够执行任意代码:

import collections.abc as cx
import typing_extensions as t

DecorateeT = t.TypeVar("DecorateeT", bound=cx.Callable[..., t.Any], contravariant=True)
ReturnT = t.TypeVar("ReturnT", covariant=True)

class Decorator(t.Protocol[DecorateeT, ReturnT]):
    def __call__(self, decoratee: DecorateeT, /) -> ReturnT: ...

这里,

DecorateeT
可以是以下任一复合语句

例如,以下都实现了一个装饰器,用

None
替换模块中的类定义:

def classErasingFunction(class_: type, /) -> None:
    return None

classErasingLambda: Decorator[type, None] = lambda _: None

class ClassRecorderAndEraser:
    _erased_classes: t.ClassVar[list[type]] = []

    def __call__(self, class_: type, /) -> None:
        type(self)._erased_classes.append(class_)
        return None

    @classmethod
    def getErasedClasses(cls) -> cx.Sequence[type]:
        return cls._erased_classes

recordAndErase = ClassRecorderAndEraser()
>>> @classErasingFunction
... class A: pass
...
>>> @classErasingLambda
... class B: pass
... 
>>> @lambda _: None  # Requires Python 3.9+; see PEP 614
... class C: pass
...
>>> @recordAndErase
... class D: pass
...
>>> @ClassRecorderAndEraser()
... class E: pass
...
>>> print(A, B, C, D, E)
None None None None None
>>> recordAndErase.getErasedClasses()
[<class 'D'>, <class 'E'>]

有了这个,下面是 Python 中

Handler
的最小实现,以在一定程度上匹配 Java API:

from __future__ import annotations

import typing_extensions as t

if t.TYPE_CHECKING:
    import collections.abc as cx
    HandlerInterfaceT = t.TypeVar("HandlerInterfaceT", bound=type["HandlerInterface"])

class _HandlerDecoratorFactory:
    _decorated_types: list[type[HandlerInterface]]

    def getDecoratedTypes(self) -> cx.Sequence[type[HandlerInterface]]:
        return self._decorated_types

    def __init__(self) -> None:
        self._decorated_types = []

    def __call__(self, HandlerClass: HandlerInterfaceT, /) -> HandlerInterfaceT:
        self._decorated_types.append(HandlerClass)
        return HandlerClass


Handler: t.Final = _HandlerDecoratorFactory()

class HandlerInterface: pass
>>> @Handler
... class MyHandler(HandlerInterface): pass
...
>>> class HandlerType1(HandlerInterface): pass
...
>>> @Handler
... class HandlerType1Implementation(HandlerType1): pass
...
>>> Handler.getDecoratedTypes()
[<class 'MyHandler'>, <class 'HandlerType1Implementation'>]

这是“Pythonic”吗?嗯,这种 API 在 Python 中是原生支持的,如果你正确地静态输入你的 API,你可以捕获相当于编译时类型错误的东西:

@Handler
class Not_A_Handler:  # mypy: Value of type variable "HandlerInterfaceT" of "__call__" of "_HandlerDecoratorFactory" cannot be "type[Not_A_Handler]"
    pass

我不会担心这是否是 Pythonic,而是会弄清楚是否需要为您的用例在 Python 中编写 Java 风格的代码。

© www.soinside.com 2019 - 2024. All rights reserved.