我如何键入提示 Python 函数 returns 派生自超级 class 的任何 class 的实例?

How do I type-hint that a Python function returns instance of any class derived from a superclass?

我有一堆 Django 模板包含标记,它们将数据库对象的特定实例或 string/int 作为参数,后者被解释为该数据库对象的主键。例如...

{% render_product product=obj %}
{% render_product product=42 %}
{% render_product product="42" %}

...一切正常,并且做的很明显:它们使用特定的 Product 实例呈现模板片段,如果需要,通过主键从数据库中获取它。这是产品和类似产品 类 的定义方式:

class Product(models.Model):
    # standard django model definition goes here

在这样的包含标签中通常会发生以下情况:

@register.inclusion_tag("render_product.html")
def render_product(product: Union[Product, str, int] = None) -> dict:
    _product = None
    if isinstance(product, Product):
        _product = product
    elif isinstance(product, str) or isinstance(product, int):
        try:
            _product = Product.objects.get(pk=product)
        except (Product.DoesNotExist, ValueError):
            pass
    return {"product": _product}

因为我在许多包含标签中都出现了相同的模式,所以我正在尝试重构它,以便我得到类似的东西:

@register.inclusion_tag("render_product.html")
def render_product(product: Union[Product, str, int] = None) -> dict:
    _product = fetch_object(Product, product)
    return {"product": _product}

这是 fetch_object 代码:

def fetch_object(cls: Type[Model] = None, obj: Union[Model, str, int] = None):
    if isinstance(obj, cls):
        return obj
    elif isinstance(obj, str) or isinstance(obj, int):
        try:
            return cls.objects.get(pk=obj)
        except (cls.DoesNotExist, ValueError):
            pass
    return None

我的问题是:我不知道如何指定该函数的 return 类型。基本上它应该类似于 "instance of any class, which is derived from Model or None"。但是如果我尝试像...

def fetch_object(
    cls: Type[Model] = None, obj: Union[Model, str, int] = None
) -> Union[Model, None]:

...然后 PyCharm 抱怨 "unresolved attribute reference" 如果我在获取的对象上访问一个方法,该方法是特定于产品的,而不是特定于模型的。

我正在尝试在我的 Python 代码中使用越来越多的类型提示,因为它已经救了我好几次了,但这是其中一种情况,我不知道正确的做法是什么,我的 google-fu 让我失望了。

fetch_object 的正确类型提示是什么?

你在这里要做的是让你的 fetch_object 函数成为 generic function.

也就是说,与其说您的函数接受任何 Type[Model],不如使用类型变量准确捕获您接受的模型类型,并指定确切的类型作为输出。例如:

from typing import TypeVar

# The bound states that T can be bound to Model or any subclass of Model.
# If the bound keyword argument is omitted, we assume the bound is 'object'.
T = TypeVar('T', bound=Model)

def fetch_object(cls: Type[T] = None, obj: Union[T, str, int] = None) -> Optional[T]:
    if isinstance(obj, cls):
        return obj
    elif isinstance(obj, str) or isinstance(obj, int):
        try:
            return cls.objects.get(pk=obj)
        except (cls.DoesNotExist, ValueError):
            pass
    return None

关于文体约定的一个小说明:为了简洁起见,我选择在此处命名 typevar T。另一个常见的约定是将您的类型变量命名为 _TModel_ModelT。即,使变量成为私有的下划线,以及为了可读性而使用更长的名称。