Search code examples
pythonclass-method

Some problems about Python inherited classmethod


I have this code:

from typing import Callable, Any


class Test(classmethod):
    def __init__(self, f: Callable[..., Any]):
        super().__init__(f)

    def __get__(self,*args,**kwargs):
        print(args)  # why out put is (None, <class '__main__.A'>) where form none  why no parameter 123
        # where was it called
        return super().__get__(*args,**kwargs)


class A:
    @Test
    def b(cls,v_b):
        print(cls,v_b)

A.b(123)

Why the output is (None, <class '__main__.A'>)? Where did None come form and why is it not the parameter 123, which is the value I called it with?


Solution

  • The __get__ method is called when the method b is retrieved from the A class. It has nothing to do with the actual calling of b.

    To illustrate this, separate the access to b from the actual call of b:

    print("Getting a reference to method A.b")
    method = A.b
    print("I have a reference to the method now. Let's call it.")
    method()
    

    This results in this output:

    Getting a reference to method A.b
    (None, <class '__main__.A'>)
    I have a reference to the method now. Let's call it.
    <class '__main__.A'> 123
    

    So you see, it is normal that the output in __get__ does not show anything about the argument you call b with, because you haven't made the call yet.


    The output None, <class '__main__.A'> is in line with the Python documentation on __get__:

    object.__get__(self, instance, owner=None)

    Called to get the attribute of the owner class (class attribute access) or of an instance of that class (instance attribute access). The optional owner argument is the owner class, while instance is the instance that the attribute was accessed through, or None when the attribute is accessed through the owner.

    In your case you are using it for accessing an attribute (b) of a class (A) -- not of an instance of A -- so that explains the instance argument is None and the owner argument is your class A.


    The second output, made with print(cls,v_b), will print <class '__main__.A'> for cls, because that is what happens when you call class methods (as opposed to instance methods). Again, from the documentation:

    When a class attribute reference (for class C, say) would yield a class method object, it is transformed into an instance method object whose __self__ attribute is C.

    Your case is described here, where A is the class, and so the first parameter (which you called cls) will get as value A.