staticmethod and classmethod should be callable

While working on PEP 579 and friends, I noticed one oddity with classmethods: for Python classes, the object stored in the class __dict__ is of type "classmethod". For extension types, the type is "classmethod_descriptor". In turns out that the latter is callable itself, unlike staticmethod or classmethod instances:
Since it makes sense to merge the classes "classmethod" and "classmethod_descriptor" (PEP 579, issue 8), one of the above behaviors should be changed. Given that adding features is less likely to break stuff, I would argue that classmethod instances should become callable. This would also make classmethod more analogous to function: you can see both "function" and "classmethod" as unbound methods. The only thing that is different is the binding behavior (binding to the instance vs. the class). Finally, function decorators typically turn functions into a different kind of callable. I find it counter-intuitive that @classmethod doesn't do that. And for consistency, also staticmethod instances should be callable. Are there any reasons to *not* make staticmethod and classmethod callable? Jeroen.

On Wed, Jun 20, 2018 at 11:56:05AM +0200, Jeroen Demeyer wrote: [...]
(The classes themselves are callable -- you're talking about the instances.) +1 yes please! The fact that classmethods and especially staticmethod instances aren't callable has been a long-running niggling pain for me. Occasionally I want to do something like this: class Spam: @staticmethod def utility(arg): # something which is conceptually related to the Spam class # but doesn't need a cls/self argument. ... value = utility(arg) but it doesn't work as staticmethod objects aren't callable until after they've gone through the descriptor protocol. I'm not the only one bitten by this: https://stackoverflow.com/questions/45375944/python-static-method-is-not-alw... https://mail.python.org/pipermail/python-list/2011-November/615069.html Part of that thread, see links and discussion here: https://mail.python.org/pipermail/python-list/2011-November/615077.html I thought I had raised a bug report for this on the tracker, but my google-fu is failing me and I can't find it. But my recollection is that the simple fix is to make staticmethod.__call__ simply delegate to the underlying decorated function. And similar for classmethod. (Of course calling classmethod instances directly won't work unless you provide the class argument. But that's just a simple matter of bound versus unbound methods.) -- Steve

On Wed, Jun 20, 2018 at 10:03 AM Serhiy Storchaka <storchaka@gmail.com> wrote:
Maybe we're misunderstanding each other? I would think that calling the classmethod object directly would just call the underlying function, so this should have to call utility() with a single arg. This is really the only option, since the descriptor doesn't have any context. In any case it should probably `def utility(cls)` in that example to clarify that the first arg to a class method is a class. -- --Guido van Rossum (python.org/~guido)

20.06.18 20:07, Guido van Rossum пише:
Sorry, I missed the cls parameter in the definition of utility(). class Spam: @classmethod def utility(cls, arg): ... value = utility(???, arg) What should be passed as the first argument to utility() if the Spam class (as well as its subclasses) is not defined still? Maybe there is a use case for calling the staticmethod descriptor. Although in this rare case I would apply the staticmethod decorator after using the function in the class body. class Spam: # @staticmethod def utility(arg): ... value = utility(arg) utility = staticmethod(utility) But I don't see a use case for calling the classmethod descriptor.

On 21 June 2018 at 03:27, Serhiy Storchaka <storchaka@gmail.com> wrote:
That would depend on the definition of `utility` (it may simply not be useful to call it in the class body, which is also the case with most instance methods). The more useful symmetry improvement is to the consistency of behaviour between instance methods on class instances and the behaviour of class methods on classes themselves. So I don't think this is a huge gain in expressiveness, but I do think it's a low cost consistency improvement that should make it easier to start unifying more of the descriptor handling logic internally. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Maybe. Though it has surprised me occasionally that pulling a classmethod or staticmethod out of the class dict (like in Jeroen's original example) doesn't work. On Wed, Jun 20, 2018 at 10:34 AM Serhiy Storchaka <storchaka@gmail.com> wrote:
-- --Guido van Rossum (python.org/~guido)

On Wed, Jun 20, 2018 at 11:56:05AM +0200, Jeroen Demeyer wrote: [...]
(The classes themselves are callable -- you're talking about the instances.) +1 yes please! The fact that classmethods and especially staticmethod instances aren't callable has been a long-running niggling pain for me. Occasionally I want to do something like this: class Spam: @staticmethod def utility(arg): # something which is conceptually related to the Spam class # but doesn't need a cls/self argument. ... value = utility(arg) but it doesn't work as staticmethod objects aren't callable until after they've gone through the descriptor protocol. I'm not the only one bitten by this: https://stackoverflow.com/questions/45375944/python-static-method-is-not-alw... https://mail.python.org/pipermail/python-list/2011-November/615069.html Part of that thread, see links and discussion here: https://mail.python.org/pipermail/python-list/2011-November/615077.html I thought I had raised a bug report for this on the tracker, but my google-fu is failing me and I can't find it. But my recollection is that the simple fix is to make staticmethod.__call__ simply delegate to the underlying decorated function. And similar for classmethod. (Of course calling classmethod instances directly won't work unless you provide the class argument. But that's just a simple matter of bound versus unbound methods.) -- Steve

On Wed, Jun 20, 2018 at 10:03 AM Serhiy Storchaka <storchaka@gmail.com> wrote:
Maybe we're misunderstanding each other? I would think that calling the classmethod object directly would just call the underlying function, so this should have to call utility() with a single arg. This is really the only option, since the descriptor doesn't have any context. In any case it should probably `def utility(cls)` in that example to clarify that the first arg to a class method is a class. -- --Guido van Rossum (python.org/~guido)

20.06.18 20:07, Guido van Rossum пише:
Sorry, I missed the cls parameter in the definition of utility(). class Spam: @classmethod def utility(cls, arg): ... value = utility(???, arg) What should be passed as the first argument to utility() if the Spam class (as well as its subclasses) is not defined still? Maybe there is a use case for calling the staticmethod descriptor. Although in this rare case I would apply the staticmethod decorator after using the function in the class body. class Spam: # @staticmethod def utility(arg): ... value = utility(arg) utility = staticmethod(utility) But I don't see a use case for calling the classmethod descriptor.

On 21 June 2018 at 03:27, Serhiy Storchaka <storchaka@gmail.com> wrote:
That would depend on the definition of `utility` (it may simply not be useful to call it in the class body, which is also the case with most instance methods). The more useful symmetry improvement is to the consistency of behaviour between instance methods on class instances and the behaviour of class methods on classes themselves. So I don't think this is a huge gain in expressiveness, but I do think it's a low cost consistency improvement that should make it easier to start unifying more of the descriptor handling logic internally. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Maybe. Though it has surprised me occasionally that pulling a classmethod or staticmethod out of the class dict (like in Jeroen's original example) doesn't work. On Wed, Jun 20, 2018 at 10:34 AM Serhiy Storchaka <storchaka@gmail.com> wrote:
-- --Guido van Rossum (python.org/~guido)
participants (5)
-
Guido van Rossum
-
Jeroen Demeyer
-
Nick Coghlan
-
Serhiy Storchaka
-
Steven D'Aprano