New submission from Andreas H. <ahanga...@gmx.net>:

(De)Serialization of in-memory data structures is an important application. 
However there is a rather unpleasant issue with ForwardRefs.


    One cannot export type aliases when they contain ForwardRefs (and expect 
things to work).


Consider the example:

    Json = Union[ List['Json'], Dict[str, 'Json'], int, float, bool, None ]


When used in another module the containing ForwardRefs cannot be resolved, or 
in the worst case, resolve to 
wrong types if in the caller namespace contains a symbol with the same name as 
ForwardRef refers to.

This of course only applies to inspection-based tools which utilize the 
run-time information, not the static type checkers.


Type aliases sometimes have to be used sometimes - they cannot be avoided in 
all cases, 
especially with recursive types as in the example above.  



There are several options to improve the situation. These are all that came to 
my mind and I want to expose them to discussion.



1. Guard with NewType

    Json = NewType('Json', Union[ List['Json'], Dict[str, 'Json'], int, float, 
bool, None ] )
   
   Advantage: This could allow automatic cross-module ForwardRef resolution 
provided issue 46369 [1] is acknowledged as a bug and fixed. 
   Disadvantage: Does not create a true type alias, but a sub-type. Type casts 
have to be used all the time, e.g.   `data = Json(bool)`.
      So can only applied to a subset of use-cases (but is IMO a clean solution 
when it fits). 


2. Use the `module` parameter of ForwardRef

    Json = Union[ List[ForwardRef('Json', module=__name__)], Dict[str, 
ForwardRef('Json', module=__name__)], int, float, bool, None ] )

   Advantage: Works in 3.10. 
   Disadvantage: Would require issue 46333 [2] to be fixed. ForwardRef is not 
meant to be instatiated by the user, 
       also `module` parameter is currently completely internal.


3. Modify ForwardRef so that it accepts a fully qualified name

    Json = Union[ List[__name__+'.Json'], Dict[str, __name__+'.Json'], int, 
float, bool, None ] )

   Advantage: This is only a tiny change (because ForwardRef has the `module` 
parameter). ForwardRef would stay internal. Less ugly than 2.
   Disadvantage: Still a bit ugly. Would also require issue 46333 [2] to be 
fixed. Relative module specification (as in relative imports) 
      would not work.


4. Manual evaluation

    Json = Union[ List['Json'], Dict[str, 'Json'], int, float, bool, None ]
    resolve_type_alias(Json, globals(), locals() )


    def resolve_type_alias(type, globalns, localns):
        class dummy:
            test: type
        typing.get_type_hints(dummy, globalns, localns) # Note: this modifies 
ForwardRefs in-place

    Advantage: Works in many versions.
    Disadvantage: Requires user to explicily call function after the last 
referenced type is defined 
       (this may be physically separated from the type alias definition, which 
does not feel like a good solution especially since 
        this ForwardRef export problem is only technical, and not even close to 
beeing obvious to most people)


5. Make `get_type_hints()` to work with type-aliases (essentially integrate 
above `resolve_type_alias`). The return value 
   of get_type_hints() would be the same as argument, just with ForwardRefs 
in-place resolved. 
 
     Json = Union[ List['Json'], Dict[str, 'Json'], int, float, bool, None ]
     get_type_hints(Json, globals(), locals())
 
   Advantage: same as 4) but hides useful (but ugly) code
   Disadvantage: same as 4)


 
6. Make all types in typing (such as List, Dict, Union, etc...) to capture 
their calling module and pass this information to ForwardRef, when 
   one is to be created. Then already during construction of ForwardRef the 
`module` will be correctly set.

     Json = Union[ List['Json'], Dict[str, 'Json'], int, float, bool, None ]
    
   Advantage: This requires no user intervention. Things will "just work"
   Disadvantage: This is rather big change. It is incompatible with the caching 
used inside typing.py (the new __module__ parameter would 
       need to be taken into account in __hash__ and/or __eq__). And maybe has 
other issues I do not see at the moment.


7. Extend `TypeAlias` hint so it can be used in bracket  way similar to e.g. 
`Annotated`

     Json = TypeAlias[ Union[ List['Json'], Dict[str, 'Json'], int, float, 
bool, None ]  ]
   

   I know, presently it is supposed to be used as  `Json: TypeAlias = Union[ 
.... ]`. But that is of no help at run-time, because
   the variable Json contains no run-time information. So even if TypeAlias 
would capture the calling module, 
   this information is not passed on to the variable `Json`. This is different 
for the bracket notation TypeAlias[ .. ]. 

   Advantage: Similar usage to Annotated. Would not require a big change such 
as in 4).
   Disadvantage: TypeAlias is not supposed to be used that way.


8. Introduce function to define a type alias, which sets the module parameter 
of all ForwardRefs

     Json = DefineTypeAlias( Union[ List['Json'], Dict[str, 'Json'], int, 
float, bool, None ] )

   DefineTypeAlias would capture its calling module and recursively walk over 
the type tree to find and patch all ForwardRefs

   Advantage: Simpler change, but requires to recurively walk over all alias 
type-hints which do not capture their calling module.  
   Disadvantage: Together with TypeAlias, things look ugly and unnecessary 
complicated. (Json: TypeAlias = DefineTypeAlias( ... ) )


Personally, I think 1) is a good solution in cases where this can be applied 
(and [1] is considered a bug and fixed). 
For the other cases 6) would be the ideal solution, but this may be too much 
work. Alternatively I think 3), 8) or 5) (in that order) 
may be interesting and could be a potential enhancement to the `typing` module.



I would really appreciate some thoughts or comments on this! Thank you. 



- [1] https://bugs.python.org/issue46369
- [2] https://bugs.python.org/issue46333

----------
components: Library (Lib)
messages: 410535
nosy: AlexWaygood, andreash, gvanrossum, kj, kumaraditya303
priority: normal
severity: normal
status: open
title: A better way to resolve ForwardRefs in type aliases across modules
type: enhancement
versions: Python 3.11

_______________________________________
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue46371>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to