Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "Modernizing Superseded Typing Features" guide #1541

Merged
merged 6 commits into from
Jan 8, 2024

Conversation

srittau
Copy link
Collaborator

@srittau srittau commented Dec 12, 2023

No description provided.

@srittau
Copy link
Collaborator Author

srittau commented Dec 12, 2023

If this guide is accepted, we can also clean up the "Best Practices" document and cross-link to the modernizing guide.

Copy link
Member

@JelleZijlstra JelleZijlstra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! A few quick comments.

docs/source/modernizing.rst Show resolved Hide resolved
docs/source/modernizing.rst Outdated Show resolved Hide resolved

*Alternative available since:* Python 3.0; Python 3.12, typing-extensions

:class:`ByteString <typing.ByteString>` was originally intended to be a type
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly this meant is very unclear. This text is probably fine though.

@srittau
Copy link
Collaborator Author

srittau commented Dec 12, 2023

Maybe it's a better idea to name this "Modernizing Superseded Typing Features", as many of those features are not technically deprecated.

@srittau srittau changed the title Add "Modernizing Obsolete Typing Features" guide Add "Modernizing Superseded Typing Features" guide Dec 12, 2023

from typing import TypeAlias # or typing_extensions.TypeAlias

IntList: TypeAlias = list[int]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might want to also mention typing_extensions.TypeAliasType, which allows providing a name and explicit typevar order. That’s newer and not all checkers support it yet though.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TeamSpen210 As I'm not terribly familiar with TypeAliasType and the new type syntax (yet), could you make a suggestion how we can word this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
IntList: TypeAlias = list[int]
IntList: TypeAlias = list[int]
There are cases where `TypeAlias` is insufficient to express what you can express
using :keyword:`type`, such as when you want the order of type parameters to
be different from how they appear in the body of the alias.
In this case you can use `typing_extensions.TypeAliasType` to express this alias
in older versions of Python::
from typing_extensions import TypeAliasType, TypeVar
T = TypeVar("T", infer_variance=True)
ListOrTuple = TypeAliasType("ListOrTuple", list[T] | tuple[T, ...], type_params=(T, ))
This is equivalent to::
type ListOrTuple[T] = list[T] | tuple[T, ...]
Note however that `TypeAliasType` is newer and not yet supported by every type checker.

Maybe something like this? I couldn't think of a good example where you'd actually want to change the order of type parameters, but it at least conveys how to use TypeAliasType to be equivalent with a type keyword.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I like your text, but I'd like to see an example where this could be useful. I want this document to be a practical guide, so keeping it concise is one of the goals.

Copy link
Contributor

@Daverball Daverball Dec 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair, until mypy supports TypeAliasType there might not be much of a point.

I think there's a potential use-case for using an unbound TypeVar as a slightly more strict gradual type compared to Any. With the old TypeAlias it's not possible to have unbound type params unless you pass them back in explicitly, so that could simplify some code, but pyright does not appear to allow unbound type params inside PEP695 type aliases and just treats it as a type parameter anyways if you ignore the error, so you still have to do it the old way and always pass in the type parameter into the alias to leave it unbound.

To illustrate what I mean:

from collections.abc import Callable
from typing import TypeVar

class Foo:
    pass

class Bar(Foo):
    pass

FooT = TypeVar("FooT", infer_variance=True, bound=Foo)

type AcceptsFoo = Callable[[FooT], object]

# I don't have to specify a type param here, because `AcceptsFoo` takes none
def foo(x: AcceptsFoo) -> None:
    return None
    
def accepts_foo(x: Foo) -> None:
    return None
    
def accepts_bar(x: Bar) -> None:
    return None

foo(accepts_foo)
# This would be rejected if FooT was actually bound
# since FooT should be contravariant, but since we
# leave it unbound it behaves like a gradual type
foo(accepts_bar)
# but this will be rejected since `int` is not a subclass of `Foo`
foo(int)

I think this is a better use-case than the explicit order of type params, but since there's no type checker support for it yet, I can't really use it as a motivating example.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another reason to use TypeAliasType is for at runtime. Since it's an instance of that type and not just an assignment, using it in type annotations will preserve the alias when introspected.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...but since there's no type checker support for [using an unbound type variable] yet...

Using a type variable that is not bound to a scope is expressly forbidden (both in PEP 484 and in PEP 695), so this will never be supported.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@erictraut That's fair, but if you modify the above example to this:

from collections.abc import Callable
from typing import TypeVar

class Foo:
    pass

class Bar(Foo):
    pass

FooT = TypeVar("FooT", infer_variance=True, bound=Foo)

type AcceptsFoo[FooT] = Callable[[FooT], object]

def foo(x: AcceptsFoo[FooT]) -> None:
    return None
    
def accepts_foo(x: Foo) -> None:
    return None
    
def accepts_bar(x: Bar) -> None:
    return None

foo(accepts_foo)
foo(accepts_bar)
foo(int)

It will work without raising any errors (beyond the last line) and will work in the way I described it, even though FooT remains unbound in foo, since it only appears in the function signature once.

To me both of those cases should be semantically equivalent. Using a singular TypeVar in functions is a common trick to achieve a gradual type like Any (i.e. it will match in either direction) but that will reject types that don't match the upper bound. But this trick tends to create quite verbose function signatures, which could be avoided if the above were allowed.

@srittau
Copy link
Collaborator Author

srittau commented Jan 8, 2024

Are there any more comments? Otherwise I'd like to merge this.

docs/source/modernizing.rst Outdated Show resolved Hide resolved
docs/source/modernizing.rst Outdated Show resolved Hide resolved
docs/source/modernizing.rst Outdated Show resolved Hide resolved
docs/source/modernizing.rst Outdated Show resolved Hide resolved
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
@srittau srittau merged commit d427993 into python:main Jan 8, 2024
3 checks passed
@srittau srittau deleted the obsolete-features branch January 8, 2024 15:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants