我想在运行时搜索我的 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 为中心的角度来看待它的。
似乎并没有实现完全相同的目标;它似乎完全适用于包装器,而这只是 Java@decorator
语法的一种可能用途。@
这是不对的。在 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 风格的代码。