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