Descriptors

Descriptors are objects with __get__, __set__, and __del__. These are used as class attributes. Their behaviors get trigger upon attribute lookup.

classmethod

class myclassmethod:
    def __init__(self, func):
        self.func = func
 
    def __get__(self, obj, objtype=None):
        if objtype is None:
            objtype = type(obj)
        
        return lambda *args,**kwargs: self.func(objtype, *args, **kwargs)
class Person:
    age = 1
 
    get_age = myclassmethod(lambda cls: cls.age)
Person.get_age()
1
person = Person()
person.get_age()
1

Here, get_age is a function attribute which a descriptor. When get_age is accessed, the descriptor behavior is triggered which returns a bounded lambda, that’s when called calls the bounded lambda.

Looking the parameters for __get__, self is the descriptor instance itself, obj is the class instance and objtype is the class.

The way descriptor is access determines who arguments are being passed to __get__.

In more sophisticated manner this can be done as follows.

from types import MethodType
 
class myclassmethod:
    def __init__(self, func):
        self.func = func
 
    def __get__(self, obj, objtype=None):
        if objtype is None:
            objtype = type(obj)
        
        return MethodType(self.func, objtype)
class Person:
    age = 1
 
    @myclassmethod
    def get_age(cls):
        return cls.age
Person.get_age()
1

staticmethod

class mystaticmethod:
    def __init__(self, func):
        self.func = func
 
    def __get__(self, obj, objtype=None):
        return self.func
class Person:
    @mystaticmethod
    def hi():
        return "hi"
Person.hi()
'hi'
person = Person()
person.hi()
'hi'

Well, static method neither bounds to class nor instance. So, decescriptor is just returning the same function back.

Dynamic Attributes

class Length:
    def __get__(self, obj, objtype=None):
        return len(obj.value)
 
class String:
    length = Length()
    def __init__(self, value):
        self.value = value
 
string = String("hi")
string.length
2
string2 = String("bye")
string2.length
3