Semantics of `__type_variables__`
JelleZijlstra opened this issue · 3 comments
I am looking at implementing this part of the PEP: https://peps.python.org/pep-0695/#accessing-type-parameters-at-runtime
The specification says that the __type_variables__
field contains all active type variables, including those from enclosing scopes. So we would want this behavior:
def outer[T]():
def inner(): pass
assert inner.__type_variables__ == (T,)
In practice, I think this would most commonly affect behavior for methods of generic classes.
I have a few problems with this behavior:
- It makes it impossible to distinguish at runtime between functions that are themselves generic, and functions that are merely within another generic function. For example, in the above,
outer.__type_variables__
andinner.__type_variables__
would be the same. - It's complicated to implement. It would require injecting cell variables for every nested function and class into the symbol table.
- The motivation in the PEP relies on stringified annotations, but it mostly does not apply under PEP 649, which is very likely where we're headed. Under PEP 649, functions that use a type variable defined in an outer scope would simply capture them in a closure. The feature is most useful under
from __future__ import annotations
, but that mode is about to be deprecated.
So I would like to change the behavior so that __type_variables__
(or __type_params__
, #8) only holds the type variables defined in the current class/function/type alias, not those inherited from enclosing scopes.
That sounds reasonable to me. I presume the same approach would apply to classes and type aliases.
The behavior that's currently spec'ed made more sense with the older scoping rules and in the absence of PEP 649.
Thanks. Concretely I would propose to solve this and #8 by renaming the attribute to __type_params__
on all of type aliases, functions, and classes. (Currently the PEP specifies __type_variables__
on classes and functions, and __parameters__
on type aliases.)
I'm not wedded to the name, but here's my reasoning:
- We should use the same name for the attribute on all of type aliases, classes, and functions, because in all three cases the attribute represents the same concept.
- "Parameters" instead of "variables" because the attribute includes ParamSpecs and TypeVarTuples as well as TypeVars.
- Prefix it with
type
for clarity, since we are adding the attribute to core objects that might have other kinds of parameters. - "params" instead of "parameters" for brevity. "Param" is a well-established abbreviation and we already use it for ParamSpec.
Went with the above approach in the proposed PEP change.