Python дженерики что такое

Введение в аннотации типов Python. Продолжение

Python дженерики что такое. image loader. Python дженерики что такое фото. Python дженерики что такое-image loader. картинка Python дженерики что такое. картинка image loader
Автор иллюстрации — Magdalena Tomczyk

В первой части статьи я описал основы использования аннотаций типов. Однако несколько важных моментов остались не рассмотрены. Во-первых, дженерики — важный механизм, во-вторых иногда может оказаться полезным узнать информацию об ожидаемых типах в рантайме. Но начать хотелось с более простых вещей

Предварительное объявление

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

Чтобы это исправить, допустимо использовать строковый литарал. В этом случае аннотации будут вычислены отложенно.

Так же вы можете обращаться к классам из других модулей (конечно, если модуль импортирован): some_variable: ‘somemodule.SomeClass’

Вообще говоря, в качестве аннотации можно использовать любое вычислимое выражение. Однако рекомендуется их делать максимально простыми, чтобы утилиты статического анализа могли их использовать. В частности, скорее всего ими не будут поняты динамически вычислимые типы. Подробнее про ограничения тут: PEP 484 — Type Hints # Acceptable type hints

Например, следующий код будет работать и даже аннотации будут доступны в рантайме, однако mypy на него выдаст ошибку

UPD: В Python 4.0 планируется включить отложенное вычисление аннотаций типов (PEP 563), которое позволит избавиться от этого приема со строковыми литералами. с Python 3.7 можно включить новое поведение с помощью конструкции from __future__ import annotations

Функции и вызываемые объекты

Для ситуаций, когда необходимо передать функцию или другой вызываем объект (например, в качестве callback) нужно использовать аннотацию Callable[[ArgType1, ArgType2. ], ReturnType]
Например,

На текущий момент невозможно описать сигнатуру функции с переменным числом параметров определенного типа или указать именованные аргументы.

Generic-типы

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

Такие типы как List или Callable, которые, мы видели раньше как раз используют механизм дженериков. Но кроме стандартных типов, вы можете создать свои дженерик-типы. Для этого надо, во-первых, завести TypeVar переменную, которая будет атрибутом дженерика, и, во-вторых, непосредственно объявить generic-тип:

Как вы можете заметить, для generic-типов работает автоматический вывод типа параметра.

Также, при определении TypeVar вы можете ограничить допустимые типы:

Иногда анализатор статический анализатор не может корректно определить тип переменной, в этом случае можно использовать функцию cast. Её единственная задача — показать анализатору, что выражение имеет определённый тип. Например:

Также это может быть полезно для декораторов:

Работа с аннотациями во время выполнения

Источник

Несколько подводных камней статической типизации в Python

Python дженерики что такое. image loader. Python дженерики что такое фото. Python дженерики что такое-image loader. картинка Python дженерики что такое. картинка image loader

Думаю, мы все потихоньку уже привыкаем, что у Python есть аннотации типов: их завезли два релиза назад (3.5) в аннотации функций и методов (PEP 484), и в прошлом релизе (3.6) к переменным (PEP 526).

Так как оба этих PEP были вдохновлены MyPy, расскажу, какие житейские радости и когнитивные диссонансы подстерегали меня при использовании этого статического анализатора, равно как и системы типизации в целом.

Disclamer: я не поднимаю вопрос о необходимости или вредности статической типизациии в Python. Просто рассказываю о подводных камнях, на которые натолкнулся в процессе работы в статически-типизированном контексте.

Дженерики (typing.Generic)

Однако, что делать, если мы пишем библиотеку, и программист, использующий ее не будет пользоваться статическим анализатором?
Заставлять пользователя инициализировать класс значением, а потом хранить его тип?

Как-то не user-friendly.
А что, если хочется сделать так?

Поэтому я написал себе небольшой дескриптор, решающий эту проблему:

Конечно, в последствие, надо будет переписать для более универсального использования, но суть понятна.

[UPD]: Разработчик typing Иван Левинский сказал, что оба варианта могут непредсказуемо сломаться.

Anyway, you can use whatever way. Maybe __class_getitem__ is even slightly better, at least __class_getitem__ is a documented special method (although its behavior for generics is not).

Функции и алиасы

Да, с дженериками вообще не просто:
К примеру, если мы где-то принимаем функцию как аргумент, то ее аннотация автоматически превращается из ковариантной в контрвариантную:

И в принципе, претензий к логике у меня нет, только решать это приходится через дженерик-алиасы:

Обратная совместимость

Cтруктурное наследование (Stuctural Suptyping)

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

C другой стороны, MyPy, в свою очередь, ведет себя более умно, и подсвечивает несовместимость типов:

Перегрузка операторов

Совсем свежая тема, т.к. при перегрузке операторов с полной типобезопасностью пропадает все веселье. Этот вопрос уже не раз всплывал в баг-треккере MyPy, но он до сих пор кое-где ругается, и его можно смело выключать.
Поясняю ситуацию:

То же касается и перегрузки любых методов сабклассов вида:

Кое-где предупреждения уже переехали в документацию, кое-где пока срабатывают на проде. Но общее заключение контрибьютеров: оставить такие перегрузки допустимыми.

Источник

Дженерики / шаблоны в питоне?

Как Python обрабатывает сценарии универсального / шаблонного типа? Скажем, я хочу создать внешний файл «BinaryTree.py» и заставить его обрабатывать двоичные деревья, но для любого типа данных.

Таким образом, я мог передать ему тип пользовательского объекта и получить двоичное дерево этого объекта. Как это делается в Python?

10 ответов

Python использует типизацию утки, поэтому ему не требуется специальный синтаксис для обработки нескольких типов.

Если вы из C ++ фона, вы помните, что, пока операции, используемые в функции / классе шаблона, определены для некоторого типа T (на уровне синтаксиса), вы можете использовать этот тип < > в шаблоне.

Итак, в основном, это работает так же:

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

После того, как я придумал несколько хороших мыслей о создании универсальных типов в Python, я начал искать тех, у кого была такая же идея, но я не смог найти ни одной. Итак, вот оно. Я попробовал это, и это работает хорошо. Это позволяет нам параметризировать наши типы в python.

Теперь вы можете получать типы из этого универсального типа.

Это решение упрощенное и имеет свои ограничения. Каждый раз, когда вы создаете универсальный тип, он создает новый тип. Таким образом, несколько классов, наследующих List( str ) в качестве родителя, будут наследоваться от двух отдельных классов. Чтобы преодолеть это, вам нужно создать dict для хранения различных форм внутреннего класса и вернуть ранее созданный внутренний класс, а не создавать новый. Это предотвратит создание дублированных типов с одинаковыми параметрами. Если интересно, более элегантное решение может быть сделано с помощью декораторов и / или метаклассов.

Поскольку python динамически типизирован, это очень просто. На самом деле, вам придется проделать дополнительную работу, чтобы ваш класс BinaryTree не работал с каким-либо типом данных.

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

На самом деле теперь вы можете использовать дженерики в Python 3.5+. См. PEP-484 и типирование документации библиотеки.

Согласно моей практике, это не очень легко и понятно, особенно для тех, кто знаком с Java Generics, но все еще пригоден для использования.

Если вы используете Python 2 или хотите переписать Java-код. Их не реальное решение для этого. Вот что я получаю за ночь: https://github.com/FlorianSteenbuck/python-generics Я до сих пор не получил компилятор, так что вы сейчас используете его так:

Задачи

Поскольку Python динамически типизирован, типы объектов во многих случаях не имеют значения. Лучше принять что-нибудь.

Чтобы продемонстрировать, что я имею в виду, этот класс дерева примет что-нибудь для своих двух ветвей:

И это можно было бы использовать так:

С этим новым метаклассом мы можем переписать пример в ответе, на который я ссылаюсь как:

Этот подход имеет несколько приятных преимуществ

К счастью, были предприняты некоторые усилия для общего программирования на python. Существует библиотека: универсальная

Он не прогрессировал в течение многих лет, но вы можете иметь общее представление о том, как использовать и создавать свою собственную библиотеку.

Источник

Как работать с типизацией в Python

Авторизуйтесь

Как работать с типизацией в Python

Первые упоминания о подсказках типов в языке программирования Python появились в базе Python Enhancement Proposals (PEP-483). Такие подсказки нужны для улучшения статического анализа кода и автодополнения редакторами, что помогает снизить риски появления багов в коде.

В этой статье мы рассмотрим основы типизации кода Python и ее роль в динамически-типизированном языке, эта информация будет наиболее полезна для начинающих Python-разработчиков.

Типизация в Python

Для обозначения базовых типов переменных используются сами типы:

Пример использования базовых типов в python-функции:

Также есть более абстрактные типы, например:

На первом месте стоит массив типов входных параметров, на втором — тип возвращаемого значения.

Про остальные абстрактные типы контейнеров можно прочитать в документации Python.

Также Python позволяет определять свои Generic-типы.

В данном примере TypeVar означает переменную любого типа, которую можно подставить при указании. Например:

На месте KeyType или ValueType могут быть конкретные типы.

Зачем это нужно

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

Конечно, можно написать и проще:

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

Использование Pydantic помогает корректно валидировать данные, при этом тип автоматически поменяется на требуемый.

Как можно заметить, более строгая типизация кода помогает сделать его проще и безопаснее. Однако, использование некоторых возможностей Pydantic может нежелательно повлиять на код. Так, мутация данных при валидации способна привести к тому, что тип значения модели будет непонятен. Например:

В данном примере созданный User после валидации будет иметь отличный от того, который был указан в модели. Это ведет к возможным крупным багам, которые лучше всегда избегать.

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

В данном примере эндпоинт /item автоматически валидирует входящий json и передает его в функцию как требуемую модель.

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

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

Нововведения Python 3.9.0

Заключение

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

Источник

Generics¶

Defining generic classes¶

Programs can also define new generic classes. Here is a very simple generic class that represents a stack:

Using Stack is similar to built-in container types:

Type inference works for user-defined generic types as well:

Construction of instances of generic types is also type checked:

Generic class internals¶

Generic types could be instantiated or subclassed as usual classes, but the above examples illustrate that type variables are erased at runtime. Generic Stack instances are just ordinary Python objects, and they have no extra runtime overhead or magic due to being generic, other than a metaclass that overloads the indexing operator.

Defining sub-classes of generic classes¶

User-defined generic classes and generic classes defined in typing can be used as base classes for another classes, both generic and non-generic. For example:

Generic can be omitted from bases if there are other base classes that include type variables, such as Mapping[KT, VT] in the above example. If you include Generic[. ] in bases, then it should list all type variables present in other bases (or more, if needed). The order of type variables is defined by the following rules:

Generic functions¶

Generic type variables can also be used to define generic functions:

As with generic classes, the type variable can be replaced with any type. That means first can be used with any sequence type, and the return type is derived from the sequence item type. For example:

Note also that a single definition of a type variable (such as T above) can be used in multiple generic functions or classes. In this example we use the same type variable in two generic functions:

A variable cannot have a type variable in its type unless the type variable is bound in a containing generic class or function.

Generic methods and generic self¶

You can also define generic methods — just use a type variable in the method signature that is different from class type variables. In particular, self may also be generic, allowing a method to return the most precise type known at the point of access.

This feature is experimental. Checking code with type annotations for self arguments is still not fully implemented. Mypy may disallow valid code or allow unsafe code.

In this way, for example, you can typecheck chaining of setter methods:

Note also that mypy cannot always verify that the implementation of a copy or a deserialization method returns the actual type of self. Therefore you may need to silence mypy inside these methods (but not at the call site), possibly by making use of the Any type.

Variance of generic types¶

Let us illustrate this by few simple examples:

This function needs a callable that can calculate a salary for managers, and if we give it a callable that can calculate a salary for an arbitrary employee, it’s still safe.

List is an invariant generic type. Naively, one would think that it is covariant, but let us consider this code:

Type variables with value restriction¶

By default, a type variable can be replaced with any type. However, sometimes it’s useful to have a type variable that can only have some specific types as its value. A typical example is a type variable that can only have values str and bytes :

This is actually such a common type variable that AnyStr is defined in typing and we don’t need to define it ourselves.

We can use AnyStr to define a function that can concatenate two strings or bytes objects, but it can’t be called with other argument types:

Note that this is different from a union type, since combinations of str and bytes are not accepted:

In this case, this is exactly what we want, since it’s not possible to concatenate a string and a bytes object! The type checker will reject this function:

Another interesting special case is calling concat() with a subtype of str :

Type variables with upper bounds¶

In a call to such a function, the type T must be replaced by a type that is a subtype of its upper bound. Continuing the example above,

Type parameters of generic classes may also have upper bounds, which restrict the valid values for the type parameter in the same way.

A type variable may not have both a value restriction (see Type variables with value restriction ) and an upper bound.

Declaring decorators¶

One common application of type variable upper bounds is in declaring a decorator that preserves the signature of the function it decorates, regardless of that signature.

Note that class decorators are handled differently than function decorators in mypy: decorating a class does not erase its type, even if the decorator has incomplete type annotations.

Here’s a complete example of a function decorator:

From the final block we see that the signatures of the decorated functions foo() and bar() are the same as those of the original functions (before the decorator is applied).

The bound on F is used so that calling the decorator on a non-function (e.g. my_decorator(1) ) will be rejected.

Decorator factories¶

Functions that take arguments and return a decorator (also called second-order decorators), are similarly supported via generics:

Sometimes the same decorator supports both bare calls and calls with arguments. This can be achieved by combining with @overload :

Generic protocols¶

The main difference between generic protocols and ordinary generic classes is that mypy checks that the declared variances of generic type variables in a protocol match how they are used in the protocol definition. The protocol in this example is rejected, since the type variable T is used covariantly as a return type, but the type variable is invariant:

This example correctly uses a covariant type variable:

See Variance of generic types for more about variance.

Generic protocols can also be recursive. Example:

Generic type aliases¶

Type aliases can be imported from modules just like other names. An alias can also target another alias, although building complex chains of aliases is not recommended – this impedes code readability, thus defeating the purpose of using aliases. Example:

A type alias does not define a new type. For generic type aliases this means that variance of type variables used for alias definition does not apply to aliases. A parameterized generic alias is treated simply as an original type with the corresponding type variables substituted.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *