Thanks, I suggested that above too. I like that it mirrors the way things would work in implementation files.
Regarding @jorenham’s suggestion to put the decorator above @overload on the first overload: It feels very subtle to depend on the ordering. In PEP 702 I recommended that @overload be placed above @deprecated for runtime introspection reasons, but that text didn’t make it into the typing spec and today only mypy enforces it (with a note). All type checkers currently treat your suggested code as deprecating only the first overload.
The importance of decorator ordering is a very common thing in Python, not just in Python. For example with abc.abstractmethod and pretty much everything involving @property, staticmethod, and classmethod.
I can’t even think of cases where decorator order doesn’t matter in the standard library right now .
So I don’t think that it’s something we should worry about being confusing.
Deprecating a function seems like it should behave like a type qualifier.
Maybe a crazy idea, but what if deprecated was generic, expecting a LiteralString (and maybe additionally a subtype of Callable), so one could do something like
minkowski_distance: deprecated["This function is deprecated in favor of `scipy.spatial.distance.minkowski` and will be removed in SciPy 1.20.0."]
@overload
def minkowski_distance(): ...
@overload
def minkowski_distance(): ...
I think it would be utterly confusing and inviting mistakes to attach something that applies to all overloads to only a single overload. No matter if it’s unambiguous in ordering, or if we use parameters, or if we introduce a new decorator “@deprecate_all_overloads”. It is fundamentally the wrong place for this information. Attempts to reorder the function definitions, or deleting a particular overload, or … might break this. It would invite endless confusion that yes, would be resolvable via documentation, but I would prefer if the behavior were obvious.
This is exactly how @final and @override work; they’re placed above the first @overload in stubs and apply to all overloads. So are you proposing that we should disallow that and instead add @final_all_overloads and @override_all_overloads to typing?
I think that is also a design mistake. These at least have the benefit of not applying to individual overloads, so they can’t be confused for each other.
No, the _all_overloads “solution” was another one from the list I said was bad. I think the “implementation” should be present in the stub files as the supported solution. But the point of my comment was to argue against adding it to the first overload, not necessarily arguing for a specific alternative.
As a counter-point, one of the @deprecated headers could also get lost during re-ordering of function definitions or other refactoring.
Requiring people to write extra boiler plate is generally going to make code more fragile, as well as being harder to read, write and interpret.
If you want to deprecate a function, there should be a way to do it with just a single line or statement.
Since all deprecated functions are instances of the deprecated class (definition), declaring that the function name is of type deprecated seems like a logical way to do this. But doing the same thing we do for @final also makes sense to me.
This isn’t right; deprecated is used as a decorator factory, and it produces instances which are decorators, as they have a __call__ method. The __call__ method mostly returns decorated functions as-is (with some modifications), or wraps them in another function (first-to-last relevant lines).
Having deprecated functions as an instance of deprecated would break a lot of code.
Oops. You’re right. I misread how that code works.
Then declaring them to be a member of deprecated would probably be a mistake, and I’m back to thinking the OPs suggestion is the best option suggested in this thread.