
本文探讨了如何在使用工厂方法创建类属性(特别是property)时,正确地添加类型提示,以避免类型检查器将其识别为 `any` 类型。通过自定义泛型 `property` 类,可以保留属性的类型信息,从而实现更精确的类型检查和代码提示。
在使用 Python 开发接口类时,我们经常会遇到需要创建具有相似结构的属性的情况。为了避免代码重复,可以使用工厂方法来动态生成这些属性。然而,这种方法的一个常见问题是,类型检查器(如 Mypy 或 Pyright)无法正确推断这些属性的类型,而是将其标记为 `Any` 类型。本文将介绍一种通过自定义泛型 `property` 类来解决此问题的方法。 ### 问题背景 假设我们有一个 `Interface` 类,其中包含一些使用工厂方法 `property_factory` 创建的属性: ```python from __future__ import annotations class Interface: def property_factory(name: str) -> property: """Create a property depending on the name.""" @property def _complex_property(self: Interface) -> str: # Do something complex with the provided name return name @_complex_property.setter def _complex_property(self: Interface, _: str): pass return _complex_property foo = property_factory("foo") # Works just like an actual property bar = property_factory("bar") def main(): interface = Interface() interface.foo # Is of type '(variable) foo: Any' instead of '(property) foo: str' if __name__ == "__main__": main()在这种情况下,Interface.foo 和 Interface.bar 会被类型检查器标记为 (variable) foo/bar: Any,而不是预期的 (property) foo/bar: str。这会导致类型检查的缺失和代码提示的不准确。
为了解决这个问题,我们可以自定义一个泛型 Property 类,继承自 Python 内置的 property 类,并使用 typing 模块中的 Generic 和 TypeVar 来指定属性的类型。
from typing import Any, Generic, TypeVar, overload, cast, Callable
T = TypeVar('T') # The return type
I = TypeVar('I') # The outer instance's type
class Property(property, Generic[I, T]):
def __init__(
self,
fget: Callable[[I], T] | None = None,
fset: Callable[[I, T], None] | None = None,
fdel: Callable[[I], None] | None = None,
doc: str | None = None
) -> None:
super().__init__(fget, fset, fdel, doc)
@overload
def __get__(self, instance: None, owner: type[I] | None = None) -> Callable[[I], T]:
...
@overload
def __get__(self, instance: I, owner: type[I] | None = None) -> T:
...
def __get__(self, instance: I | None, owner: type[I] | None = None) -> Callable[[I], T] | T:
return cast(Callable[[I], T] | T, super().__get__(instance, owner))
def __set__(self, instance: I, value: T) -> None:
super().__set__(instance, value)
def __delete__(self, instance: I) -> None:
super().__delete__(instance)这个 Property 类接受两个类型变量:I 表示外部实例的类型,T 表示属性的返回类型。通过重写 __init__ 和 __get__ 方法,我们可以保留属性的类型信息。
现在,我们可以使用自定义的 Property 类来创建属性。首先,我们需要定义 getter 和 setter 函数的类型:
from collections.abc import Callable
Getter = Callable[['Interface'], str]
Setter = Callable[['Interface', str], None]
def complex_property(name: str) -> tuple[Getter, Setter]:
def _getter(self: Interface) -> str:
return name # Replace with your complex logic
def _setter(self: Interface, value: str) -> None:
pass # Replace with your complex logic
return _getter, _setter然后,我们可以将这些函数传递给 Property 类的构造函数:
class Interface:
foo = Property(*complex_property("foo"))现在,我们可以使用类型检查器来验证类型提示是否正确:
instance = Interface()
reveal_type(Interface.foo) # mypy => (Interface) -> str
# pyright => (Interface) -> str
reveal_type(instance.foo) # mypy + pyright => str
instance.foo = 42 # mypy => error: Incompatible types in assignment
# pyright => error: "Literal[42]" is incompatible with "str" ('foo' is underlined)
instance.foo = 'lorem' # mypy + pyright => fine可以看到,类型检查器现在可以正确地推断出 Interface.foo 和 instance.foo 的类型为 str,并且可以在类型不匹配时发出错误。
通过自定义泛型 Property 类,我们可以有效地解决在使用工厂方法创建类属性时类型提示丢失的问题。这种方法可以提高代码的可读性和可维护性,并减少潜在的类型错误。虽然自定义 property 类稍微增加了代码的复杂性,但它带来的类型安全性和代码提示的提升是值得的。
以上就是如何为使用工厂方法创建的类属性添加类型提示的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号