
I've done some more thinking more about Serhiy's worry about changing a factory function to a class, and how that would change the meaning of type-hints. Say: def spam(x: Eggs, y:Cheese) -> _Aardvark: # actual factory function implementation # later, we use it as a function protocol def myfunction(a: int, callback: spam) -> str: ... If spam changes implementation from a factory function to the actual class of _Aardvark, keeping the name: class spam: # body of _Ardvark goes here that would completely change the meaning of the myfunction type declaration. I still think that is not a scenario we need to care about. In my mind, that counts as a user-applied footgun. But if people disagree, and Serhiy's argument persuades them, then we can still use functions as their own prototype. @Callable def spam(x: Eggs, y: Cheese) -> _Aardvark: # actual factory function implementation The Callable decorator would just flag the function as *permitted* to be used as a prototype, with no other change. Or perhaps we could have @FunctionPrototype. Alternatively, we could write the consumer's annotation like this: def myfunction(a: int, callback: Prototype[spam]) -> str: ... where Prototype[x] uses: - the signature of x if x is a function or method; - the signature of `x.__call__` if x is a callable instance; - the signature of `x.__new__` or `x.__init__` is x is a class; as the prototype. However it is spelled, we might require functions to opt-in before they can be used as prototypes, in other words the decorator is the author's promise that they aren't going to change the function into a class, or change its signature, without the usual deprecation warnings etc. -- Steve