@@ -496,6 +496,9 @@ imported qualified or with some other name)
496496 # *[... for t in ...] arguments
497497 | <ident>[<variadic-type-arg> +]
498498
499+ # Type member access (associated type access?)
500+ | <type>.<name>
501+
499502 | GenericCallable[<type>, lambda <args>: <type>]
500503
501504 # Type conditional checks are boolean compositions of
@@ -524,8 +527,8 @@ imported qualified or with some other name)
524527(``<type-bool-for> `` is identical to ``<type-for> `` except that the
525528result type is a ``<type-bool> `` instead of a ``<type> ``.)
526529
527- There are three core syntactic features introduced: type booleans,
528- conditional types and unpacked comprehension types.
530+ There are three and a half core syntactic features introduced: type booleans,
531+ conditional types, unpacked comprehension types, and type member access .
529532
530533:ref: `"Generic callables" <generic-callable >` are also technically a
531534syntactic feature, but are discussed as an operator.
@@ -572,6 +575,18 @@ comprehension iterating over the arguments of tuple type ``iter_ty``.
572575The comprehension may also have ``if `` clauses, which filter in the
573576usual way.
574577
578+ Type member access
579+ ''''''''''''''''''
580+
581+ The ``Member `` and ``Param `` types introduced to represent class
582+ members and function params have "associated" type members, which can
583+ be accessed by dot notation: ``m.name ``, ``m.type ``, etc.
584+
585+ This operation is not lifted over union types. Using it on the wrong
586+ sort of type will be an error. (At least, it must be that way at
587+ runtime, and we probably want typechecking to match.)
588+
589+
575590Type operators
576591--------------
577592
@@ -674,13 +689,14 @@ Object inspection
674689 of classes. Its type parameters encode the information about each
675690 member.
676691
677- * ``N `` is the name, as a literal string type
678- * ``T `` is the type
679- * ``Q `` is a union of qualifiers (see ``MemberQuals `` below)
692+ * ``N `` is the name, as a literal string type. Accessable with `` .name ``.
693+ * ``T `` is the type. Accessable with `` .type ``.
694+ * ``Q `` is a union of qualifiers (see ``MemberQuals `` below). Accessable with `` .quals ``.
680695 * ``Init `` is the literal type of the attribute initializer in the
681- class (see :ref: `InitField <init-field >`)
696+ class (see :ref: `InitField <init-field >`). Accessable with `` .init ``.
682697 * ``D `` is the defining class of the member. (That is, which class
683- the member is inherited from. Always ``Never ``, for a ``TypedDict ``)
698+ the member is inherited from. Always ``Never ``, for a ``TypedDict ``).
699+ Accessable with ``.definer ``.
684700
685701* ``MemberQuals = Literal['ClassVar', 'Final', 'NotRequired', 'ReadOnly'] `` -
686702 ``MemberQuals `` is the type of "qualifiers" that can apply to a
@@ -694,16 +710,6 @@ qualifier. ``staticmethod`` and ``classmethod`` will return
694710``staticmethod `` and ``classmethod `` types, which are subscriptable as
695711of 3.14.
696712
697- We also have helpers for extracting the fields of ``Members ``; they
698- are all definable in terms of ``GetArg ``. (Some of them are shared
699- with ``Param ``, discussed below.)
700-
701- * ``GetName[T: Member | Param] ``
702- * ``GetType[T: Member | Param] ``
703- * ``GetQuals[T: Member | Param] ``
704- * ``GetInit[T: Member] ``
705- * ``GetDefiner[T: Member] ``
706-
707713All of the operators in this section are :ref: `lifted over union types
708714<lifting>`.
709715
@@ -795,10 +801,10 @@ Callable inspection and creation
795801``Callable `` types always have their arguments exposed in the extended
796802Callable format discussed above.
797803
798- The names, type, and qualifiers share getter operations with
799- ``Member ``.
804+ The names, type, and qualifiers share associated type names with
805+ ``Member `` (`` .name ``, `` .type ``, and `` .quals ``) .
800806
801- TODO: Should we make ``GetInit `` be literal types of default parameter
807+ TODO: Should we make ``.init `` be literal types of default parameter
802808values too?
803809
804810.. _generic-callable :
@@ -947,8 +953,7 @@ those cases, we add a new hook to ``typing``:
947953 it before being returned.
948954
949955 If set to ``None `` (the default), the boolean operators will return
950- ``False `` while ``Iter `` will evaluate to
951- ``iter(typing.TypeVarTuple("_IterDummy")) ``.
956+ ``False `` while ``Iter `` will evaluate to ``iter(()) ``.
952957
953958
954959There has been some discussion of adding a ``Format.AST `` mode for
@@ -997,7 +1002,7 @@ The ``**kwargs: Unpack[K]`` is part of this proposal, and allows
9971002type-annotated attribute of ``K ``, while calling ``NewProtocol `` with
9981003``Member `` arguments constructs a new structural type.
9991004
1000- ``GetName `` is a getter operator that fetches the name of a ``Member ``
1005+ ``c.name `` fetches the name of the ``Member `` bound to the variable `` c ``
10011006as a literal type--all of these mechanisms lean very heavily on literal types.
10021007``GetMemberType `` gets the type of an attribute from a class.
10031008
@@ -1011,8 +1016,8 @@ as a literal type--all of these mechanisms lean very heavily on literal types.
10111016 typing.NewProtocol[
10121017 *[
10131018 typing.Member[
1014- typing.GetName[c] ,
1015- ConvertField[typing.GetMemberType[ModelT, typing.GetName[c] ]],
1019+ c.name ,
1020+ ConvertField[typing.GetMemberType[ModelT, c.name ]],
10161021 ]
10171022 for c in typing.Iter[typing.Attrs[K]]
10181023 ]
@@ -1066,9 +1071,9 @@ contains all the ``Property`` attributes of ``T``.
10661071
10671072 type PropsOnly[T] = typing.NewProtocol[
10681073 *[
1069- typing.Member[typing.GetName[p] , PointerArg[typing.GetType[p] ]]
1074+ typing.Member[p.name , PointerArg[p.type ]]
10701075 for p in typing.Iter[typing.Attrs[T]]
1071- if typing.IsAssignable[typing.GetType[p] , Property]
1076+ if typing.IsAssignable[p.type , Property]
10721077 ]
10731078 ]
10741079
@@ -1098,15 +1103,15 @@ suite, but here is a possible implementation of just ``Public``
10981103 type Create[T] = typing.NewProtocol[
10991104 *[
11001105 typing.Member[
1101- typing.GetName[p] ,
1102- typing.GetType[p] ,
1103- typing.GetQuals[p] ,
1104- GetDefault[typing.GetInit[p] ],
1106+ p.name ,
1107+ p.type ,
1108+ p.quals ,
1109+ GetDefault[p.init ],
11051110 ]
11061111 for p in typing.Iter[typing.Attrs[T]]
11071112 if not typing.IsAssignable[
11081113 Literal[True],
1109- GetFieldItem[typing.GetInit[p] , Literal["primary_key"]],
1114+ GetFieldItem[p.init , Literal["primary_key"]],
11101115 ]
11111116 ]
11121117 ]
@@ -1138,13 +1143,13 @@ dataclasses-style method generation
11381143 typing.Param[Literal["self"], Self],
11391144 *[
11401145 typing.Param[
1141- typing.GetName[p] ,
1142- typing.GetType[p] ,
1146+ p.name ,
1147+ p.type ,
11431148 # All arguments are keyword-only
11441149 # It takes a default if a default is specified in the class
11451150 Literal["keyword"]
11461151 if typing.IsAssignable[
1147- GetDefault[typing.GetInit[p] ],
1152+ GetDefault[p.init ],
11481153 Never,
11491154 ]
11501155 else Literal["keyword", "default"],
@@ -1325,75 +1330,6 @@ AKA '"Rejected" Ideas That Maybe We Should Actually Do?'
13251330
13261331Very interested in feedback about these!
13271332
1328- The first one in particular I think has a lot of upside.
1329-
1330- Support dot notation to access ``Member `` components
1331- ----------------------------------------------------
1332-
1333- Code would read quite a bit nicer if we could write ``m.name `` instead
1334- of ``GetName[m] ``.
1335- With dot notation, ``PropsOnly `` (from
1336- :ref: `the query builder example <qb-impl >`) would look like::
1337-
1338- type PropsOnly[T] = typing.NewProtocol[
1339- *[
1340- typing.Member[p.name, PointerArg[p.type]]
1341- for p in typing.Iter[typing.Attrs[T]]
1342- if typing.IsAssignable[p.type, Property]
1343- ]
1344- ]
1345-
1346- Which is a fair bit nicer.
1347-
1348-
1349- We considered this but initially rejected it in part due to runtime
1350- implementation concerns: an expression like ``Member[Literal["x"],
1351- int].name `` would need to return an object that captures both the
1352- content of the type alias while maintaining the ``_GenericAlias `` of
1353- the applied class so that type variables may be substituted for.
1354-
1355- We were mistaken about the runtime evaluation difficulty,
1356- though: if we required a special base class in order for a type to use
1357- this feature, it should work without too much trouble, and without
1358- causing any backporting or compatibility problems.
1359-
1360- We wouldn't be able to have the operation lift over unions or the like
1361- (unless we were willing to modify ``__getattr__ `` for
1362- ``types.UnionType `` and ``typing._UnionGenericAlias `` to do so!)
1363-
1364- Or maybe it would be fine to have it only work on variables, and then
1365- no special support would be required at the definition site.
1366-
1367- That just leaves semantic and philosophical concerns: it arguably makes
1368- the model more complicated, but a lot of code will read much nicer.
1369-
1370- What would the mechanism be?
1371- ''''''''''''''''''''''''''''
1372-
1373- A general mechanism to support this might look
1374- like::
1375-
1376- class Member[
1377- N: str,
1378- T,
1379- Q: MemberQuals = typing.Never,
1380- I = typing.Never,
1381- D = typing.Never
1382- ]:
1383- type name = N
1384- type tp = T
1385- type quals = Q
1386- type init = I
1387- type definer = D
1388-
1389- Where ``type `` aliases defined in a class can be accessed by dot notation.
1390-
1391-
1392- Another option would be to skip introducing a general mechanism (for
1393- now, at least), but at least make dot notation work on ``Member `` and
1394- ``Param ``, which will be extremely common.
1395-
1396-
13971333Dictionary comprehension based syntax for creating typed dicts and protocols
13981334----------------------------------------------------------------------------
13991335
@@ -1486,6 +1422,38 @@ difference? Combined with dictionary-comprehensions and dot notation
14861422(The user-defined type alias ``PointerArg `` still must be called with
14871423brackets, despite being basically a helper operator.)
14881424
1425+ Have a general mechanism for dot-notation accessible associated types
1426+ ---------------------------------------------------------------------
1427+
1428+ The main proposal is currently silent about exactly *how * ``Member ``
1429+ and ``Param `` will have associated types for ``.name `` and ``.type ``.
1430+
1431+ We could just make it work for those particular types, or we could
1432+ introduce a general mechansim that might look something like::
1433+
1434+ @typing.has_associated_types
1435+ class Member[
1436+ N: str,
1437+ T,
1438+ Q: MemberQuals = typing.Never,
1439+ I = typing.Never,
1440+ D = typing.Never
1441+ ]:
1442+ type name = N
1443+ type tp = T
1444+ type quals = Q
1445+ type init = I
1446+ type definer = D
1447+
1448+
1449+ The decorator (or a base class) is needed if we want the dot notation
1450+ for the associated types to be able to work at runtime, since we need
1451+ to customize the behavior of ``__getattr__ `` on the
1452+ ``typing._GenericAlias `` produced by the class so that it captures
1453+ both the type parameters to ``Member `` and the alias.
1454+
1455+ (Though possibly we could change the behavior of ``_GenericAlias ``
1456+ itself to avoid the need for that.)
14891457
14901458Rejected Ideas
14911459==============
@@ -1567,6 +1535,35 @@ worse. Supporting filtering while mapping would make it even more bad
15671535
15681536We can explore other options too if needed.
15691537
1538+
1539+ Don't use dot notation to access ``Member `` components
1540+ ------------------------------------------------------
1541+
1542+ Earlier versions of this PEP draft omitted the ability to write
1543+ ``m.name `` and similar on ``Member `` and ``Param `` components, and
1544+ instead relied on helper operators such as ``typing.GetName `` (that
1545+ could be implemented under the hood using ``typing.GetArg `` or
1546+ ``typing.GetMemberType ``).
1547+
1548+ The potential advantage here is reducing the number of new constructs
1549+ being added to the type language, and avoiding needing to either
1550+ introduce a new general mechanism for associated types or having a
1551+ special-case for ``Member ``.
1552+
1553+ ``PropsOnly `` (from :ref: `the query builder example <qb-impl >`) would
1554+ look like::
1555+
1556+ type PropsOnly[T] = typing.NewProtocol[
1557+ *[
1558+ typing.Member[typing.GetName[p], PointerArg[typing.GetType[p]]]
1559+ for p in typing.Iter[typing.Attrs[T]]
1560+ if typing.IsAssignable[typing.GetType[p], Property]
1561+ ]
1562+ ]
1563+
1564+ Everyone hated how this looked a lot.
1565+
1566+
15701567Perform type manipulations with normal Python functions
15711568-------------------------------------------------------
15721569
0 commit comments