Аннотации типов Python3 для функции генерации типов

Я немного запутался в аннотациях типов в python3, особенно для функции генератора, которая выдает сгенерированные типы. Я думаю, что именно моя путаница связана с документацией typing.Type. Вот мой фрагмент кода:

from collections import UserList
UserType = TypeVar('UserType')
def TypeSequence(usertype: Type[UserType]) -> Type[Sequence[UserType]]:
    class Result(UserList):
        ... # Cut out the implementation for brevity's sake
    return Result

Сгенерированный «TypeSequence» что-то делает с проверками типов, чтобы генерировать только сериализуемые структуры данных, что не важно для этого вопроса. Дело в том, что вы должны иметь возможность сделать что-то вроде этого:

MyIntSequence = TypeSequence(int)
MyIntSequence((1, 2, 3)) -> [1, 2, 3] with type Sequence[Int]


MyTupleSequence = TypeSequence(tuple)
MyTupleSequence(((1, 2), (3, 4))) -> [(1, 2), (3, 4)] with type Sequence[tuple]

Мой вопрос. Верны ли предоставленные мной аннотации типов?

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


Простой пример, когда вывод типа кажется неудачным:

Пример неудачной подсветки синтаксиса

Обратите внимание, как это отличается от этой версии списка:

Пример работы подсветки синтаксиса


Я также получаю много вопросов о том, что на самом деле делает TypeSequence. Я отредактировал эту реализацию, чтобы сделать пост более кратким, но здесь полная реализация. Он выполняет некоторые проверки типов и типов:

from collections import UserList
from typing import (Optional, Any, Sequence, Callable, Hashable, Dict, Mapping, Type, TypeVar,
)


UserType = TypeVar('UserType')
def TypeSequence(usertype: Type[UserType]) -> Type[Sequence[UserType]]:
    class Result(UserList):
        def __init__(self, *args):
            from collections import Iterable
            if len(args) == 0:
                super(Result, self).__init__()
            elif len(args) == 1:
                if not isinstance(args[0], Iterable):
                    raise ValueError("Not a iterable")
                if issubclass(usertype, tuple) and hasattr(usertype, "_fields"):
                    if any(not isinstance(x, Iterable) for x in args[0]):
                        raise ValueError("Invalid initializer for named tuple")
                    if len(args[0]) != len(usertype._fields):
                        raise ValueError(f"Not enough values to initialize {usertype}")
                    seq = (usertype(*x) for x in args[0])
                else:
                    seq = (usertype(x) for x in args[0])
                super(Result, self).__init__(seq)

    Result.__name__ = f"TypeSequence[{usertype.__name__}]"

    return Result

Я не думаю, что это что-то добавляет к вопросу, но вот: D


person PK Cakeout    schedule 25.03.2020    source источник
comment
Что такое UserList? Это не определено.   -  person a_guest    schedule 25.03.2020
comment
Можете ли вы показать пример, когда вывод типа терпит неудачу? Аннотации выглядят нормально, хотя я не вижу смысла в этой функции. Похоже, он не делает ничего больше, чем list.   -  person Wombatz    schedule 25.03.2020
comment
› Что такое список пользователей? Это не определено ~~ Извините, добавил оператор импорта.   -  person PK Cakeout    schedule 25.03.2020
comment
Что на самом деле делает TypeSequence? Кажется, это не связано с определением функции-генератора в Python, которая использует yield для создания значения типа generator.   -  person chepner    schedule 25.03.2020
comment
@chepner Я не думаю, что оператор означает генератор Python, но использует генератор слов в более общем смысле.   -  person juanpa.arrivillaga    schedule 25.03.2020
comment
@PKCakeout Вам нужно использовать UserList, а не просто возвращать подкласс list? Тогда ваш возвращаемый тип может быть просто Type[List[UserType]].   -  person chepner    schedule 25.03.2020
comment
@chepner Да, это не генератор, это функция генерации типов. Функция, которая генерирует типы. Я ожидал, что он запутается.   -  person PK Cakeout    schedule 25.03.2020
comment
Ко всем остальным: я добавил большую часть дополнительной информации. Последний раздел, вероятно, не очень важен, но поскольку людям было любопытно, я добавил полную реализацию.   -  person PK Cakeout    schedule 25.03.2020
comment
Есть ли причина, по которой вы определяете класс Result(UserList)? Класс не имеет никакой дополнительной функциональности по сравнению с обычным list. Фабричная функция формы Callable[[Iterable[T]], List[T]] кажется более подходящей, чем класс.   -  person MisterMiyagi    schedule 25.03.2020
comment
Обратите внимание, что Mypy правильно выводит тип x_seq, но отвергает XSeq([X()])Sequence не принимает никаких аргументов. В принципе, поведение дальнейшего кода для проверки типов не определено. Использование List вместо Sequence делает ваш код хорошо типизированным, а использование Callable вместо Type (как в связанном вопросе) также удовлетворяет PyCharm. def TypeSequence(usertype: Callable[..., UserType]) -> Callable[[Iterable[UserType]], Sequence[UserType]].   -  person MisterMiyagi    schedule 25.03.2020


Ответы (1)


TLDR: используйте Callable вместо Type для создания экземпляра любого типа. В частности, четко укажите сигнатуру возвращаемого типа.

def TypeSequence(
    usertype: Type[UserType]
) -> Callable[[Iterable[UserType]], Sequence[UserType]]
    ...

Type[Sequence[UserType]] не может быть создан, так как Sequence является абстрактным типом. mypy помечает создание как недействительное:

XSeq = TypeSequence(X)
x_seq = XSeq([X()])  # error: Too many arguments for "Sequence"

Для корректности типов аннотируйте возвращаемый тип как List или UserList.

def TypeSequence(usertype: Type[UserType]) -> Type[UserList[UserType]]:
    ...

Помимо правильности типов, имейте в виду, что PyCharm обычно не понимает сложные отношения Type. Выявление типа функции показывает, что Type[UserList[UserType]] упрощается до Type[UserList].

Вместо этого использование Callable позволяет выразить инстанцирование сложного типа. Можно определить точную подпись, включая Sequence вместо UserList:

def TypeSequence(usertype: Type[UserType]) -> Callable[[Iterable[UserType]], Sequence[UserType]]:
    ...
person MisterMiyagi    schedule 25.03.2020
comment
Извиняюсь. Прошло довольно много времени — я не мог вернуться к рассматриваемому коду в последние недели, но теперь я вернулся. Я понятия не имею, почему ваше решение получило -1 голос. Кажется, это работает и имеет смысл! Первоначальный повторяющийся маркер в моем вопросе также мог быть правильным, но я больше не вижу его. Спасибо за подробное объяснение, на самом деле имеет смысл, что Sequence не указывает, как он инициализируется! Не думал об этом, и это имеет большой смысл. Не уверен, что я вообще придерживаюсь здесь UserList, поэтому в этом случае я буду придерживаться решения Callable. - person PK Cakeout; 17.04.2020