如何在 Python 中输入提示返回泛型类型并符合协议的函数
如果我有一个函数接受 T
类型的实例,并输出相同的实例,但经过修改,使其另外符合 Protocol
,我应该如何输入提示?我的主要目标是让我的 IDE (VSCode) 知道返回的对象具有原始对象的所有属性 + “添加”协议中定义的所有属性。
具体来说,我尝试通过以下方式将“元数据感知”添加到来自第三方库的一堆对象(代码稍微简化以仅关注类型提示):
@runtime_checkable
class HasMetadata(Protocol):
"""Protocol for objects which have a `metadata` property."""
@property
def metadata(self) -> Dict[str, Any]: ...
class MetadataMixin(HasMetadata):
"""A mixin addig a `metadata` property to an existing class."""
def __init__(self, *args, metadata: Optional[Dict[str, Any]] = None, **kwargs):
"""
Adds a metadata dictionary to this instance and calls the superclass ctor.
Args:
metadata (optional): The metadata to associate to this object. Defaults to
None, meaning an empty dictionary.
"""
super().__init__(*args, **kwargs)
self._metadata = metadata or {}
@property
def metadata(self) -> Dict[str, Any]:
return self._metadata
# Here's what I fail to type-hint correctly
T = TypeVar('T')
def inject_metadata(obj: T, metadata: Optional[Dict[str, Any]] = None) -> ??? # In swift I'd write something like `T & HasMetadata`
"""Dynamically injects metadata in the given object."""
if isinstance(t, HasMetadata):
raise TypeError('Cannot inject metadata in an object that already has it.')
# Create a new type which acts like the old but also has `MetadataMixin` in its
# mro, then change the type of the given object to this new type. This will give
# the object the `metadata` property.
old_type = type(t)
new_type_name = f'_{old_type.__name__}WithMetadata'
# Retrieve the new type if it was already created, or create it otherwise.
# Simplified for demo purposes.. just always create the new type.
# Also don't mind about the Mixin added last as a base class; I know technically it
# should come first to be a true Mixin but that doesn't matter here.
new_type = type(new_type_name, (old_type, MetadataMixin), dict(old_type.__dict__))
obj.__class__ = new_type
# Now set the actual metadata.
metadata = metadata or {}
setattr(obj, '_metadata', metadata)
return obj
我知道我可以做到这一点通过创建辅助类型来为特定的 - 预先已知的 - 类工作,例如:
class Bar:
pass
class Baz(Bar, HasMetadata):
@property
def metadata(self) -> Dict[str, Any]: ...
def inject_metadata(obj: Bar, ...) -> Baz:
...
但这只有在我具体知道我想要注入元数据的类型时才有效。理想情况下,我会使用任何 T< /代码>。
If I have a function taking in an instance of type T
, and outputting that same instance but modified so it additionally conforms to a Protocol
, how should I type hint that? My main goal is to allow my IDE (VSCode) to know that the returned object has all attributes from the original object + all attributes defined in the 'added' protocol.
Specifically, I'm trying to add 'metadata-awareness' to a bunch of objects coming from a 3rd-party library in the following way (code simplified a bit to focus on the type-hinting only):
@runtime_checkable
class HasMetadata(Protocol):
"""Protocol for objects which have a `metadata` property."""
@property
def metadata(self) -> Dict[str, Any]: ...
class MetadataMixin(HasMetadata):
"""A mixin addig a `metadata` property to an existing class."""
def __init__(self, *args, metadata: Optional[Dict[str, Any]] = None, **kwargs):
"""
Adds a metadata dictionary to this instance and calls the superclass ctor.
Args:
metadata (optional): The metadata to associate to this object. Defaults to
None, meaning an empty dictionary.
"""
super().__init__(*args, **kwargs)
self._metadata = metadata or {}
@property
def metadata(self) -> Dict[str, Any]:
return self._metadata
# Here's what I fail to type-hint correctly
T = TypeVar('T')
def inject_metadata(obj: T, metadata: Optional[Dict[str, Any]] = None) -> ??? # In swift I'd write something like `T & HasMetadata`
"""Dynamically injects metadata in the given object."""
if isinstance(t, HasMetadata):
raise TypeError('Cannot inject metadata in an object that already has it.')
# Create a new type which acts like the old but also has `MetadataMixin` in its
# mro, then change the type of the given object to this new type. This will give
# the object the `metadata` property.
old_type = type(t)
new_type_name = f'_{old_type.__name__}WithMetadata'
# Retrieve the new type if it was already created, or create it otherwise.
# Simplified for demo purposes.. just always create the new type.
# Also don't mind about the Mixin added last as a base class; I know technically it
# should come first to be a true Mixin but that doesn't matter here.
new_type = type(new_type_name, (old_type, MetadataMixin), dict(old_type.__dict__))
obj.__class__ = new_type
# Now set the actual metadata.
metadata = metadata or {}
setattr(obj, '_metadata', metadata)
return obj
I know I can make this work for specific - known up front - classes by creating helper types, e.g.:
class Bar:
pass
class Baz(Bar, HasMetadata):
@property
def metadata(self) -> Dict[str, Any]: ...
def inject_metadata(obj: Bar, ...) -> Baz:
...
but that only works if I know specifically which type(s) I want to inject metadata in. Ideally I'd make this work with any T
.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论