PEP 695: Type Parameter Syntax

Congrats! :partying_face: I’m so excited to see it was accepted and can’t wait to see it implemented! :smiley:
Couple of questions though:

  1. When will the PEP’s status be officially updated?
  2. Does it mean we will be able to see it in Python 3.12? After all, we’re about to enter the beta phase meanning that new features aren’t supposed to be added and this feature will need some time to be implemented.
  3. Is there any way for outside users like myself to be updated with the schedules of when such decisions are made? From my perspective, my only option is to enter this (and other pep threads im interested in) every day and check if there is an update, but is there some public schedule which we can look at to know when such decision should be made?
2 Likes

You could subscribe to the official issue for the SC request.

  1. When somebody makes a PR to the PEPs repo to update it.
  2. Yes, it should go into 3.12, and yes, that means we should start implementing it soon. Hopefully @erictraut can submit his draft implementation and we can iterate on that.
  3. I think subscribing to the GitHub - python/steering-council: Communications from the Steering Council repo is the best way to stay up to date with SC decisions. There is no set schedule; the SC is a small group of volunteers who do things when they have the time to do so.

@erictraut Congrats with the SC acceptance! We have only about 3 weeks to get this into 3.12 – the beta 1 feature freeze is May 8 (PEP 693 – Python 3.12 Release Schedule | peps.python.org). And during that time falls PyCon, which means I have less time to focus on this. Do you want to start by merging the current main branch into your prototype, or would you prefer to start over? (The merge alone could be a big job.)

I hope there are volunteers to help with this process. (IIRC Eric’s prototype is linked from the PEP.)

3 Likes

My earlier prototype is in this branch, but it implements an older version of the spec (prior to incorporating input about scoping rules). The cpython compiler code has also changed significantly in the past year, so I don’t think it’s worth trying to carry the prototype forward in its entirety.

There are parts of the prototype that can be used with little or no modification. This includes:

  1. Grammar changes
  2. AST changes (Note: these changes don’t yet include the suggestions that @cdce8p made.)
  3. Unit tests
  4. Changes to typing.py classes

I’ve manually integrated these changes into a new branch that is synced with the latest cpython main branch.

This leaves the following work:
5. Symtable changes
6. Compiler changes
7. Moving TypeVar, TypeVarTuple, ParamSpec and Generic implementations from Python to C

I’m hoping that we can get a core Python developer or someone who has expertise with the symtable and compiler components to volunteer to implement the necessary changes. I’m happy to collaborate and share learnings from my proof of concept, but I’d prefer that someone else take the lead here. Any takers?

One consideration is whether PEP 696 (Type defaults for TypeVarLIkes) is going to be pursued for Python 3.12. If so, it might make sense to combine the implementation effort for 695 and 696. Given the short time window available, PEP 696 may need to wait until Python 3.13. @Gobot1234, what is the status of 696? Have you submitted it to the SC for review?

3 Likes

It has been submitted and is at the top of our list for discussion next week.

6 Likes

Haven’t seen this mentioned but I’m a bit confused with type constraint syntax.

PEP suggests the following

class Foo[AnyStr: (bytes, str)]:
    ...

This for me reads more like a “tuple with bytes and str” rather then “union of bytes and str” which it is. Wouldn’t the following be more consistent with existing syntax?

class Foo[AnyStr: bytes | str]:
    ...

I assume people are already familiar with union operator and this feels to me like a more natural way to write such constraint.

Otherwise great PEP, really looking forward to it!

That would mean something else; it would be equivalent to TypeVar("AnyStr", bound=bytes | str) instead of TypeVar("AnyStr", bytes, str).

1 Like

Indeed, thank you!

@erictraut Would you need any help with the required AST changes or the parser in general? If so, I’d be happy to help out, so feel free to reach out to me!

Would you mind enabling issues on your CPython fork at GitHub - erictraut/cpython: The Python programming language?

We can then use issues there to discuss and track the implementation.

I’m planning to start out with implementing TypeVar and co in C.

2 Likes

Thanks Jelle. I just enabled issues and discussions in my fork.

1 Like

May I suggest a change to the proposed syntax?

Instead, may I suggest the following?

  • a < b means a subtypes b
  • a > b means a supertypes b
  • a in (b, …) means a is one of b, …

<, <:, <= are all common notations in the type theory literature for the subtype relation. (Scala uses <: and >:.) Personally, I like < in this case because it is the most visually concise.

Thoughts? I think it’s worth getting this right early, to pay off dividends in the long run.

This was discussed exhaustively in the lead-up to the PEP, and the PEP contains a survey of the syntax in other languages. : is reasonably common in other languages, and that’s what we ended up with.

1 Like

In function signatures and inline type annotations, it means a is a subtype. I don’t think it means “has type” in any situation?

Consistent with what? The tuple notation isn’t used anywhere else as far as I know?

I’m fairly sure foo: Foo means foo is an instance of Foo (or an instance of a subtype of Foo), where Foo is not necessarily a class, outside of this PEP. I think that’s what they mean by “has type”.

1 Like

The PEP states

We propose to deprecate the existing typing.TypeAlias introduced in PEP 613. The new syntax eliminates its need entirely.

But as far as I can tell, if you want to use the type alias at runtime, you still need typing.TypeAlias. Consider:

Python 3.12.0b1 (main, Jun  1 2023, 14:31:32) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from typing import TypeAlias
>>> List: TypeAlias = list
>>> List(x*2 for x in range(3))
[0, 2, 4]
>>> type List = list
>>> List(x*2 for x in range(3))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'typing.TypeAliasType' object is not callable

It seems the type alias created by type X = Y can only be used in annotations and not as a class.

So, what’s the guidance going forward? Are “run-time type aliases” a bad idea for some reason?

Probably new style type aliases should be made callable, similar to types.GenericAlias. @erictraut @Jelle

This could be thought of as a feature rather than a bug.

One of the motivations for PEP 613 was so that type checkers could more easily differentiate between variable assignments and type-alias assignments. By using your List type alias as a callable at runtime, you’re in some ways subverting the intended purpose of PEP 613.

I think it’s quite nice that the new-style syntax for type aliases enforces a restriction against runtime use – it emphasises the idea that these are special kinds of assignments that are specifically intended for typing.

Also, unlike generic aliases, many (most?) type aliases won’t have values that are callable. Instances of types.UnionType aren’t callable; nor is Literal[1, 2, 3, 4, 5], etc. New-style type aliases also natively support forward references; resolving these aliases could result in NameError if done at the wrong place, so these obviously won’t be callable either.

If you really need to call a new-style type alias at runtime, you can always call it like this (though I have no idea how happy type checkers will be about it):

type List = list
List.__value__(x*2 for x in range(3)
2 Likes

Alex stated it well, and I agree with what he said.

In general, I don’t think type aliases should be used in value expressions at runtime. As Alex said, if you need to access the value of a TypeAliasType, you can use the __value__ attribute.

If your intent is to create an alias of a class symbol that can be used anywhere that the class can be used (as shown in your example), you should use a variable, not a type alias (List = list). This has the added benefit that type checkers will not specialize the class, allowing it to be specialized later based on the context in which it is used. Contrast that to List: TypeAlias = list where list is assumed to be specialized to list[Any].

2 Likes