Python typing deprecation - python

The latest typing docs has a lot of deprecation notices like the following:
class typing.Deque(deque, MutableSequence[T])
A generic version of collections.deque.
New in version 3.5.4.
New in version 3.6.1.
Deprecated since version 3.9: collections.deque now supports []. See PEP 585 and Generic Alias Type.
What does this means? Should we not use the generic type Deque (and several others) anymore? I've looked at the references and didn't connect the dots (could be because I'm an intermediate Python user).

It means that you should be transitioning to using built-in types / types from the standard library instead of the ones provided by typing. So for example collections.deque[int] instead of typing.Deque[int]. The same for list, tuple, etc. So tuple[int, str] is the preferred way.

See PEP585 and Generic Alias Type.
Changes in Python 3.9 remove the necessity for a parallel type hierarchy in the typing module, so that you can just use collections.deque directly when annotating a deque-like type.
For example, it means that annotating a deque of ints like
def foo(d: typing.Deque[int]):
...
Should be changed to:
def foo(d: collections.deque[int]):
...

Related

Leetcode. What are the function arguments types?

I quote this example: 27. Remove Element https://leetcode.com/problems/remove-element/
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
I want to code in my own editor, eg Pycharm. But the skeleton for filling the answer has List type. Sometimes, there is Optional in the argument.
These are not standard Python type. But when used in Leetcode, it works. So everytime, I have to remove them when editing in my own editor.
I'm curious as to why it works in Leetcode but not in my editor ? Is there something I can do on my own editor to use List ? If it's a specific type on Leetcode, then I'm curious as to why Leetcode does not use the standard python list ?
They are names that must be imported from the typing module:
from typing import List, Optional
You can avoid importing them if you use from __future__ import annotations, which will prevent annotations from being evaluated at runtime, or if you use Python 3.11 (ETA late 2022) or later, in which deferred annotations will be the norm.
typing.List, among others, is deprecated, as Python 3.9 introduced the ability to use list[int] in place of List[int]. Others, like Optional, have no corresponding type in Python proper.

Type alias with union

I currently have this type alias, and some associated functions in my code:
Constant = int
def operation(data: Union[Constant, OtherTypes]):
if isinstance(data, Constant):
# do something
else:
# do something else
Now, I would like for Constant to also represent another type, say float. This Constant alias is used throughout my codebase, so I'd like to not have to change it everywhere.
I have tried:
Constant = (int, float)
This works nicely with isinstance, but the Unions complain that "TypeError: Union[arg, ...]: each arg must be a type."
I have then tried:
Constant = Union[int, float]
Now, the issues come with the isinstance; I get "TypeError: Subscripted generics cannot be used with class and instance checks".
Is there a way to do what I am trying to achieve ?
Thanks.
isinstance supporting Unions comes with python 3.10 . As such, starting from that version, the second solution will work.
See https://peps.python.org/pep-0604/ .
As mentioned by Pankkake in their answer, for Python 3.10 you can simply do Constant = int | float and it will work everywhere.
However, if you must support older versions of Python, you can use the solutions provided in Check a variable against Union type at runtime in Python 3.6 by Frank, MSeifert and Richard Xia:
Python 3.8+
Use the typing.get_args(tp) function to get a tuple with the union types, which you can use inside isinstance:
from typing import Union, get_args
Constant = Union[int, float]
def operation(data: Union[Constant, OtherTypes]):
if isinstance(data, get_args(Constant)):
# do something
else:
# do something else
get_args only returns the type's arguments without validating if the type is an Union or other generic type, which seems enough for your requirement.
If for some reason you also need to check at runtime if the Constant type is an Union specifically, use the typing.get_origin(tp) function:
from typing import Union, get_origin
if get_origin(Constant) is Union:
# do something
Python 3.5.3+
Before 3.8 the get_args and get_origin functions didn't exist, so you needed to use the undocumented attributes __args__ and __origin__ instead.
def operation(data: Union[Constant, OtherTypes]):
if isinstance(data, Constant.__args__):
# do something
else:
# do something else
This still works for 3.10.5 but, since these attributes are undocumented, the snippet above could stop working with no short notice in any future Python version.
Python 3.5.0 to 3.5.2
Type hints were implemented in Python's 3.5.0 version. Up to 3.5.2 the attribute name to get an union's arguments was __union_params__:
def operation(data: Union[Constant, OtherTypes]):
if isinstance(data, Constant.__union_params__):
# do something
else:
# do something else
Of course, this attribute only exists for Union types, so if you need to check if a type is an Union, check for the existence of the attribute.
Note that this only works up to Python 3.5.2, since in 3.5.3 they changed the attribute name to __args__.

How to type-annotate a file-like object in Python 3.8+

Previously typing.IO, typing.TextIO, and typing.BinaryIO were available to annotate file-like objects, e.g. those returned by open(). However, after Python 3.8 they seem to be deprecated, and will be removed in Python 3.12. Unlike many of the other deprecations like typing.List[T], which is replaced by list[T], these IO types have no clear migration path.
How should file-like object types be annotated in modern Python, going forward?
Python 3.9 docs has an unclear notice:
"These types are also in the typing.io namespace, which was never supported by type checkers and will be removed."
See more in the discussion "[docs] Confusing deprecation notice for typing.IO"
In the new version of the docs it's fixed:
"The typing.io namespace is deprecated and will be removed. These types should be directly imported from typing instead."

Should `isinstance()` check against typing or collections.abc?

Both typing and collections.abc includes similar type such as Mapping, Sequence, etc.
Based on the python documentation, it seems that collections.abc is preferred for type checking:
This module provides abstract base classes that can be used to test whether a class provides a particular interface; for example, whether it is hashable or whether it is a mapping.
https://docs.python.org/3/library/collections.abc.html
but using typing also works and I'd rather not import Mapping from both typing and collections.abc. So is there any catch in using typing with isinstance()?
As of python 3.9, the entire suite of collections.abc aliases in the typing module is deprecated, meaning that your preferred option for python >=3.9 should be to use the versions in the collections.abc module. The classes will not be removed from the typing module for a few years to come, however.
Many of the typing generic classes are just aliases to the abc ones. Just as an example from the docs, Hashable:
class typing.Hashable
An alias to collections.abc.Hashable
Also,
isinstance(abc.Hashable, typing.Hashable)
isinstance(typing.Hashable, abc.Hashable)
are both True, making it clear they are equivalent in terms of the class hierarchy. In Python you can check an alias's origin using the origin field:
>>> typing.Hashable.__origin__
<class 'collections.abc.Hashable'>
No reason I can see to import abc if you do not need it. The two packages provide close but different uses, so I would import only what you need. This is more of an opinion, but if your use-case is only checking against isinstance (i.e. no interesting argument annotation) both seem equivalent to me, though abc may be the more usual tool for this.
There is no catch. The typing library uses stubs from the collections library. You are totally allowed to use isinstance with the typing library, certainly for the types you mentioned (Mapping and Sequence).

How to properly function annotate / type hint a list of strings

I am trying to figure out how to properly function annotate or type hint a list of strings. For example, if I had a function like this:
def send_email(self, from_address: str, to_addresses: list[str]):
pass
to_addresses should be a list of strings. But when I try to use that annotation I get the following error in my Python 3.4.3 interpreter:
TypeError: 'type' object is not subscriptable
I am positive the list[str] is causing the issue, because if I change it to str the error goes away, but that doesn't properly reflect my intentions for the parameter.
Python 3.4 doesn't specify a format for its function annotations, it merely provides a mechanism that allows you to use any expression as the annotation. How the annotations are interpreted is up to you and the libraries you use.
Python 3.5 standardizes the way function annotations are used for type hinting, as documented in PEP 484. To annotate a list of strings, you use List[str], where List is imported from the typing module. You can also use Sequence[str] if your function accepts any list-like sequence, or Iterable[str] for any iterable.
Starting with Python 3.9, you can use list[str] as a type annotation, which doesn't require importing anything.
In Python 3.9+, list (with a lowercase l) can be used in type annotations and your code should work as is. On older versions of Python you need to import typing.List and use it instead
from typing import List
to_addresses: List[str]
Note the capital L.
You might want to consider something more specific, e.g.
import typing
Address = typing.NewType("Address")
See NewType docs
The static type checker will treat the new type as if it were a subclass of the original type
This syntax is now valid in Python 3.9+:
In type annotations you can now use built-in collection types such as list and dict as generic types instead of importing the corresponding capitalized types (e.g. List or Dict) from typing.
Prior to 3.9 though, you need to use the imported List or in Python 3.7+ you can add
from __future__ import annotations
at the top of your file, which allows using list[int] (for example). Note though, this import only affects annotations:
from __future__ import annotations only affects annotations -- just the things after the colon. It makes it so that annotations are never evaluated
It still doesn't allow list[int] in arbitrary contexts.

Categories