Congrats! Iām so excited to see it was accepted and canāt wait to see it implemented!
Couple of questions though:
When will the PEPās status be officially updated?
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.
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?
When somebody makes a PR to the PEPs repo to update it.
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.
@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.)
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:
Grammar changes
AST changes (Note: these changes donāt yet include the suggestions that @cdce8p made.)
Unit tests
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?
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!
@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!
For upper bound specification, letting a: b denote āa subtypes bā is a bit inconsistent because elsewhere it means āahas typebā.
For constrained type specification, letting a: (b, ā¦) denote āa is one of b, ā¦ā is a bit inconsistent for the same reason.
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.
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ā.
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?
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)
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].