classification
Title: Correct classmethod emulation in Descriptor HowTo Guide
Type: behavior Stage: resolved
Components: Documentation Versions: Python 3.9
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: rhettinger Nosy List: docs@python, eric.araujo, ezio.melotti, maggyero, mdk, rhettinger, willingc
Priority: normal Keywords:

Created on 2019-06-08 11:51 by maggyero, last changed 2020-10-24 23:08 by maggyero. This issue is now closed.

Pull Requests
URL Status Linked Edit
PR 13912 closed maggyero, 2019-06-08 11:51
PR 22934 maggyero, 2020-10-24 23:07
Messages (6)
msg345031 - (view) Author: Géry (maggyero) * Date: 2019-06-08 11:51
With the current Python equivalent `ClassMethod` implementation of `classmethod` given in Raymond Hettinger's _Descriptor HowTo Guide_, the following code snippet:

```
class A:
    @ClassMethod
    def f(cls, *, x): pass

print(A.f)
A.f(x=3)
```

prints:

> <function ClassMethod.\_\_get\_\_.<locals>.newfunc at 0x106b76268>

and raises:

> TypeError: newfunc() got an unexpected keyword argument 'x'

instead of only printing:

> <bound method A.f of <class '\_\_main\_\_.A'>>

like the `@classmethod` decorator would do.

So the `ClassMethod` implementation fails in two regards:
* it does not return a bound method to a class;
* it does not handle keyword-only arguments.

With this PR `ClassMethod` will correctly emulate `classmethod`. This approach (`types.MethodType`) is already used in the Python equivalent `Function` implementation of functions given earlier in the same guide.
msg345052 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2019-06-08 15:48
This wasn't intended to be an exact equivalent. Instead, it focuses on the key action of classmethod which is prepending the class to the call.  Though less accurate, the current version communicates better.  I suggest adding a short comment to the effect that "newfunc" emulates "types.MethodType(self.f, klass)".
msg345064 - (view) Author: Géry (maggyero) * Date: 2019-06-08 21:07
@Raymond Hettinger

> Though less accurate, the current version communicates better

I agree that types.MethodType is more accurate but may be less understandable. But in this case I think that the Function class for emulating instance methods should not use types.MethodType either, but a custom newfunc function as well. And **kwargs parameters should be added to both newfunc functions.
msg345066 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2019-06-08 21:20
I'm going to close this because 1) I'm working on a somewhat major set of updates this guide already and 2) I think this tracker issue misses the point of what those guide is trying to do (communicating that the class is prepended to the argument stream but not trying to be a fully accurate drop in substitute).  The responsibility for describing in detail what @classmethod does belongs in libstdtypes.rst.  The goal in the descriptor how-to is to give an understanding of how descriptors work.
msg345074 - (view) Author: Géry (maggyero) * Date: 2019-06-09 07:09
@Raymond Hettinger

> The goal in the descriptor how-to is to give an understanding of how descriptors work.

Okay. So I don't know if that was clear in my last message but that also means replacing the current "Function" implementation:

class Function(object):
    . . .
    def __get__(self, obj, objtype=None):
        "Simulate func_descr_get() in Objects/funcobject.c"
        if obj is None:
            return self
        return types.MethodType(self, obj)

with something like this:

class Function(object):
    . . .
    def __get__(self, obj, objtype=None):
        "Simulate func_descr_get() in Objects/funcobject.c"
        if obj is None:
            return self
        def newfunc(*args, **kwargs):
            return self(obj, *args, **kwargs)
        return newfunc
        # "newfunc" emulates "types.MethodType(self, obj)"

And as you said, adding a similar comment to the "ClassMethod" implementation (and **kwargs):

class ClassMethod(object):
    "Emulate PyClassMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, klass=None):
        if klass is None:
            klass = type(obj)
        def newfunc(*args, **kwargs):
            return self.f(klass, *args, **kwargs)
        return newfunc
        # "newfunc" emulates "types.MethodType(self.f, klass)"
msg345092 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2019-06-09 20:52
How about taking another look at this after I've finished my more extensive rewrite based on my course materials.
History
Date User Action Args
2020-10-24 23:08:42maggyerosetversions: + Python 3.9, - Python 3.7
2020-10-24 23:08:29maggyerosetresolution: not a bug -> fixed
2020-10-24 23:07:52maggyerosetpull_requests: + pull_request21866
2019-06-09 20:52:21rhettingersetmessages: + msg345092
2019-06-09 07:09:31maggyerosetmessages: + msg345074
2019-06-08 21:20:23rhettingersetstatus: open -> closed
resolution: not a bug
messages: + msg345066

stage: resolved
2019-06-08 21:07:32maggyerosetmessages: + msg345064
2019-06-08 15:48:39rhettingersetmessages: + msg345052
2019-06-08 15:42:23rhettingersetassignee: docs@python -> rhettinger
2019-06-08 11:51:27maggyerocreate