注释需要类对象而不是该类实例的函数参数的正确方法是什么?
在下面的示例中,
some_class
参数预计是类型实例(这是一个类),但是这里的问题是
type
太宽泛:
def construct(some_class: type, related_data:Dict[str, Any]) -> Any:
...
在
some_class
需要一组特定类型对象的情况下,使用
type
根本没有帮助。
typing
模块可能需要一个类泛型来执行此操作:
def construct(some_class: Class[Union[Foo, Bar, Baz]], related_data:Dict[str, Any]) -> Union[Foo, Bar, Baz]:
...
在上面的示例中,
some_class
是
Foo
、
Bar
或
Faz
类,而不是它的实例。它们在类树中的位置并不重要,因为
some_class: Class[Foo]
也应该是有效的情况。因此,
# classes are callable, so it is OK
inst = some_class(**related_data)
或
# instances does not have __name__
clsname = some_class.__name__
或
# an operation that only Foo, Bar and Baz can perform.
some_class.a_common_classmethod()
对于 mypy、pytype、PyCharm 等应该没问题。
当前的实现(Python 3.6 或更早版本)如何做到这一点?
是对的,
typing.Type
太宽泛,无法指定一组特定的类。在 Python 3.7 及更高版本中,可以使用
typing.Type
附带的方括号语法来实现想要的:
from typing import Type, Dict, Any, Union
class Foo:
...
class Bar:
...
class Baz:
...
def construct(some_class: Type[Union[Foo, Bar, Baz]], related_data: Dict[str, Any]) -> Union[Foo, Bar, Baz]:
return some_class(**related_data)
# 正确使用
construct(Foo, {})
construct(Bar, {})
construct(Baz, {})
# 错误使用 - 会被类型检查器标记
construct(int, {})
在这种情况下,
Type[Union[Foo, Bar, Baz]]
表示
some_class
参数必须是
Foo
、
Bar
或
Baz
类的类型对象。
对于 Python 3.6 或更早版本,没有直接的方法来实现这种类型提示。可以使用字符串字面量来近似表示:
def construct(some_class: 'Type[Union[Foo, Bar, Baz]]', related_data: Dict[str, Any]) -> Union[Foo, Bar, Baz]:
return some_class(**related_data)
但这不会提供运行时类型检查。
另一种选择是使用类型注释工具(例如 MyPy)提供的
typing_extensions
包中的
Protocol
来定义一个协议,指定所需的类方法或属性:
from typing import Protocol, Dict, Any
class MyProtocol(Protocol):
def a_common_classmethod(self) -> None:
...
def construct(some_class: Type[MyProtocol], related_data: Dict[str, Any]) -> Any:
some_class.a_common_classmethod()
...
这种方法允许根据行为而不是具体的类来约束类型。
总而言之,使用
typing.Type
和方括号语法是在 Python 3.7 及更高版本中注释类对象类型注释的首选方法。对于旧版本,可以使用字符串字面量或
Protocol
来实现类似的结果。