The only place I can find/recall referencing this perceived problem was in the (now stalled) https://discuss.python.org/t/pep-727-documentation-metadata-in-typing proposal. There the primary suggestion floating around was reusing the @ operator, but Annotated usage is only increasing, well beyond the purposes of that PEP, so i figured i’d at least start a dedicated thread for it; at a minimum as future reference if everyone else thinks this is an awful idea.
tl;dr
I think foo: [int, Doc("foo")] → foo: Annotated[int, Doc("foo")] is least bad.
Followed by foo: int with Doc("foo") (which i initially preferred before writing this), although i’m personally open to whatever syntax would make this a thing.
Rationale
Use ofAnnotatedtoday is annoying for a number of reasons, and it is increasingly used for declarative libraries like pydantic or dataclasses to associate data with type attributes
The need to import Annotated
I think, as much as is reasonable, typing constructs would ideally be automatically in scope to remove the need to import them like list vs List; or else have syntax which obviates their use, like |vs Union
line noise between variable and type
foo: Annotated[int, <something else>]versusfoo: int <something else>
The more complex the overall annotation, or whether it spans more than one line, the more visually annoying it becomes that the annotation forces the actual type away from the variable in question.
Length
foo: Annotated[int, foo] is just particularly long.
foo: int foo is literally the shortest it could be, but more realistically…
foo: int . foo where . is some sigil or…
foo: int word foo where word is some keyword.
Trying to minimize length seems worthwhile, but i think the interruption and noise around the type itself is more problematic than the length necessarily
interrupting keyboard/typing-flow
You’re making a pydantic model, you think oh yes, foo: list. but then you need a default, ah crap, move left, type Annotated[, move right, type , Field(default_factory=list)]
I would prefer to be able to complete the thought foo: list, and then optionally add annotations onto the end, also because that naturally fits with its purpose
Considered options
Sigils (as an infix operator)
foo: int @ Something()
# or
foo: int @ (
Something(),
VeryLonnnnnnng(),
)
Pros
- Sigils approach the minimum bound of characters
- Depending on whether the sigil is already an operator, it can possibly be backported to prior versions syntax-wise (whether or not it would be)
Cons
- They’re less obvious/more arbitrary than a decent keyword
- Seems more likely to devolve into bikeshedding
Keywords (as infix operator)
foo: int with Something()
# or
foo: int with (
Something(),
VeryLonnnnnnng(),
)
with seems like the only sane existing keyword really… is, as dont seem to convey the right meaning. and for, pass, and are the only remaining 3 that are even in the ballpark.
I assume a new keyword is out of the question for this specific feature by itself.
Pros
- A keyword seems more clear/natural
Cons
- Slightly longer than sigils
- for
within particular, it would not be backwards syntax compatible
Abbreviation
e.g. foo: [int, Something()]
Pros
- short
- backwards compatible syntax-wise
- Most similar to what we have now
Cons
- (imo) ideally it would be pure additional syntax after the variable/type in order to solve the line-noise point.
- With that said, it’s 1 character, so it’s still much better
Chaining
For both above infix operator styles, I think including more than one Annotated item at a time would need to happen with commas and require parentheses in order to span lines. I had initially been thinking foo with x with b, but there’s be no sane way to line wrap it.
Thus foo: int with Something(), SomethingElse() would translate exactly to foo: Annotated[int, Something(), SomethingElse()]
It would ultimately be up to formatters, but I would anticipate a long multiline annotation to be slightly shorter and arguably look better
class Foo:
variable: Annotated[
int,
Doc("Some long doc string that just keeps going"),
]
# vs
class Foo:
variable: int with (
Doc("Some long doc string that just keeps going")
)