Как я могу напечатать подсказку, что функция Python возвращает экземпляр любого класса, производного от суперкласса? - PullRequest
0 голосов
/ 29 октября 2018

У меня есть куча тегов включения шаблонов Django, которые принимают в качестве аргумента либо конкретный экземпляр объекта базы данных, либо строку / int, которая интерпретируется как первичный ключ этого объекта базы данных. Например ...

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

... все работают нормально и делают очевидное: они визуализируют фрагмент шаблона с конкретным экземпляром Product, выбирая его по первичному ключу из базы данных, если это необходимо. Вот как определяются класс 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

Моя проблема: я понятия не имею, как указать тип возвращаемого значения этой функции. По сути, это должно быть что-то вроде «экземпляра любого класса, производного от Model или None». Но если я попробую что-то вроде ...

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

... тогда PyCharm жалуется на «неразрешенную ссылку на атрибут», если я получаю доступ к методу извлеченного объекта, который зависит от продукта, а не от модели.

Я пытаюсь использовать все больше и больше подсказок в моем коде Python, потому что это уже несколько раз спасло мою задницу, но это один из тех случаев, когда я понятия не имею, как правильно поступить это было бы, и мое Google-фу подводит меня.

Как правильно указывать тип для fetch_object?

1 Ответ

0 голосов
/ 29 октября 2018

То, что вы хотите сделать здесь, это сделать вашу fetch_object функцию обобщенной функцией .

То есть вместо того, чтобы просто сказать, что ваша функция принимает любой 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. То есть подчеркивание, делающее переменную частной, и более длинное имя для удобства чтения.

...