I am proposing adding syntax intended for annotating a function or class itself, not the function’s return type.
Specifically, I’m proposing a :: operator analogous to ->, and its annotation would get stored in the function/class’s __annotations__ under the key "__self__", though of course feel free to suggest alternative spellings for both the operator and key.
For example:
def foo() :: "a":
...
class Thing :: "a":
...
print(foo.__annotations__)
print(Thing.__annotations__)
Would print out:
{'__self__': 'a'}
{'__self__': 'a'}
Side note: I went with "__self__" because it has to live alongside annotations for class variables in a class’s __annotations__.
For functions, :: can be used alongside ->, though if both are specified, :: must be first. So def bar() :: "a" -> None: ... is fine, but def bar() -> None :: "a": ... is not. I think it makes sense for “an attribute for the function” to bind more tightly than “an attribute for the function’s return value” (also, with this spelling, putting the -> first looks like it’s annotating the annotation).
As far as “why add this”, that’s unfortunately difficult since we’re talking about arbitrary metadata I’m intentionally not prescribing a meaning to.
Obviously, it’s not directly useful for typehinting. Nobody needs class Foo :: type: ... to know that Foo is a type. But it could be used in other ways.
If code is currently using return value annotations def foo() -> Annotated[None, 5]: ... to both typehint the return value and store some other arbitrary value of no consequence for typechecking, it gives you an alternative, namely def foo() :: 5 -> None: ....
And of course classes can’t currently do that all, even with Annotated.
If you actually want to know the specific thing that spawned this suggestion, it was this draft PEP and its accompanying discussion about a typing.sealed.
One of the rejected ideas in it was to explicitly list all the options, but that was rejected because forward reference concerns made it impossible since it wasn’t being used in an annotation, because there’s no obvious place for the annotation to live, and any syntax changes would be too specific for one feature.
Well, this would be the obvious place for something like that.
class Node() :: Sealed[Leaf, Branch]:
...
Because it’s actually an annotation, it’s subject to the deferred evaluation so it wouldn’t choke on forward references.
Of course, that’s simply the inspiration, I’m not (currently) proposing a typing.Sealed like that. This thread is simply about the syntax.
Additionally, while I don’t feel strongly about this either way (because I don’t see how it could be useful), but it makes sense syntactically, so I’ll throw it out here - annotating a class’s bases. Maybe something like:
class Base:
...
class Derived(Base: "a"):
...
print(Base.__annotations__)
print(Derived.__annotations__)
would print:
{}
{'__bases__': {<class '__main__.Base'>: 'a'}}
If this has been discussed to death, I apologize - I could not find any prior discussions. Searching for things like “class annotations” and “function annotations” gives a ton of results.
Thanks.