Thanks @erictraut! I was waiting to hear your feedback on my response above about it.
Thought Process
Let me first explain my thought process, maybe we can catch something I’m misunderstanding.
Maybe the first thing I might be confusing is what a “symbol” would be.
I see that Annotated can be used in two main places:
- Annotating a type hint: after the
: in a parameter, variable, etc. Or in the return of a function. I’m considering all those type hints.
- To create a type alias. After the
=. In this case, a new object is created (the new type alias).
I would understand that the main use case for Annotated is type hints.
I also see that in PEP 593 they expected that people would normally create type aliases for the places where they would have used Annotated directly, just for the verbosity: PEP 593 – Flexible function and variable annotations | peps.python.org
Then, if we think about a “symbol”, I would think that Annotated creates a new symbol when used as a type alias (after the =, something is being assigned to), but I wouldn’t think it creates a new symbol for a type hint (after a :). When used in a type hint I would think it is only adding metadata to the same type information for that parameter symbol.
So, with respect to Annotated, I wouldn’t really think that a type alias symbol would be comparable to a variable with a type hint symbol.
Then I thought that the main use case would be to consider Doc() for the things that are annotated with type hints (e.g. after :). In that case, a type alias would actually not have documentation on its own, but only when applied to a type hint…
Nevertheless, I wrote that a single annotated type alias would have its own doc until used as an annotation in case that’s useful (although I don’t really see much use for it). Also because it’s not possible to add Annotated to the type of a type alias, i.e. this is not valid:
UserName: Annotated[TypeAlias, Doc("The type alias of a username")] = Annotated[
str, Doc("The user's name")
]
Then, also, I don’t really imagine a use case where it would be useful to have documentation for an isolated type alias, only when used as a type hint.
Problems of Type Aliases
Now, considering the use cases you mention would be cumbersome because metadata doesn’t follow a formal system:
- Type arguments
- Unions
- Type Guards
In all these cases, if a type alias was not used but a pure type instead, that same type could have been directly annotated with Annotated and with a Doc, and in the same way, the meaning of the documentation there would be more or less undefined, right?
So, would this be a problem of type aliases or of the way Annotated could be “freely” nested in other type structures and places?
Following that train of thought, maybe this would mean that Annotated with Doc would not really document the parameter of a function but only its type, or something like that, it’s just a pedantic difference, but being very formal, maybe that’s what it would mean… and in that case, that is not really very useful, it’s only useful when it’s documenting the parameter of the function.
Practical Use Cases
Now, apart from the strict correctness of where should it apply or not (and if I’m misunderstanding things or not), let’s consider the actual main use cases this would intend to cover.
I think the main use cases are the very simple ones. E.g. Independent of type aliases, I don’t see a reason why it would be helpful now to support nesting Annotated with Doc, for example in type parameters, unions, or type guards. At least not with the current state of the language and tools.
Maybe it would make sense to explicitly mention that Annotated with Doc only has a meaning when used at the top level of a type hint, e.g. right after the :. This would discard Annotated with Doc inside a union and other cases like that.
To clarify, this would be unspecified and would have no meaning because Annotated with Doc is inside a union:
def create_user(name: Annotated[str, Doc("The user's name")] | None = None):
...
Instead this would be valid because Annotated with Doc is at the top level:
def create_user(name: Annotated[str | None, Doc("The optional user's name")] = None):
...
Would that make sense?
Type Alias Use Cases
Now, about type aliases, as several have mentioned, it would have huge potential for this if there was a way to allow defining Annotated with Doc in a type alias that could be re-used.
For example, I could imagine many APIs could be simplified by re-using the same type alias for the same parameter in multiple functions. It would also help a lot with the discomfort of having long signatures. In fact, it would also reduce the function body as the docstring would also be smaller.
Or extending it further, I could imagine it would be very useful to declare some common parameters with their documentation in a typed dict, and then unpack it in **kwargs. This would not be strictly a type alias, but quite similar. And it would keep type correctness for the signature types, while providing docs for the parameters, and reducing a lot the code duplication in the library.
On the other hand, I personally don’t see much use in having a documentation string associated just with the same type alias symbol independent of anything else, I can’t imagine a use case for that yet.
Going Forward
Is there a balance we could strike here to allow Annotated with Doc in type alias for parameters? Or at least some of these scenarios?
The first thing I would think and suggest we do is limit the support of Doc in an Annotated type alias to only when it is used at the top level of a type hint, i.e. only when the type alias is used right after the : or in the return of a function. The Doc used in an Annotated type alias used anywhere else would have no meaning… I would think this would discard all the other cumbersome scenarios (e.g. type arguments, unions, type guards, etc.).
If you definitely don’t see a way this could be viable, the second thing I would suggest would be just removing anything specific to type aliases from the PEP, saying that this PEP only specifies the usage of Annotated directly in the type annotations, making it explicit that it leaves type aliases unspecified for now, just to leave the “open door” for future PEPs to extend (or restrict) it further in case there’s a way to make this work in the future.
Please let me know what you think of all this, I would like to know your thoughts before updating the PEP.