What is descriptor?
(Of course, some geeky definition coming)
According to official python3 docs, this is what they are saying
In general, a descriptor is an object attribute with “binding behavior”, one whose attribute access has been overridden by methods in the descriptor protocol. Those methods are __get__(), __set__(), and __delete__(). If any of those methods are defined for an object, it is said to be a descriptor.
Now it seems pretty clear that we need a class and and in that class we need to define these methods and that's it. Let's try out
class A:
def __get__(self, instance, owner):
return instance.__dict__[self.name]
def __set__(self, instance, value):
print("setting the attribute")
instance.__dict__[self.name] = value
def __delete__(self, instance):
del instance.__dict__[self.name]
def __set_name__(self, owner, name):
self.name = name
Now we want to use this descriptor
class B:
a = A()
i = B()
print(i.a)
# KeyError: 'a'
i.a = "wysiwyg"
# setting the attribute
print(i.a)
# wysiwyg
Use cases
(Okay, enough chit-chat let's fight, sorry sorry let's understand how we can use this concept)
So suppose you want validation before setting a value or getting the value. But wait there are getters and setters, right? Yes, there are but I am a fan of DRY and I solemnly swear to not repeat except necessary. So even if the condition is same you have to use setter each time for each attribute.
class CheckString:
def __set_name__(self, owner, name):
self.name = name
def __set__(self, instance, value):
if isinstance(value, str):
instance.__dict__[self.name] = value
else:
raise ValueError("Value must be string")
class User:
name = CheckString()
email = CheckString()
def __init__(self, name, email):
self.name = name
self.email = email
u = User("Sumit", 1)
# ValueError: Value must be string
Now if we want to implement the same with property how we would have
class User:
def __init__(self, name, email):
self._name = name
self._email = email
@property
def email(self):
return self._email
@email.setter
def email(self, value):
if isinstance(value, str):
self._email = value
else:
raise ValueError("Value must be string")
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if isinstance(value, str):
self._name = value
else:
raise ValueError("Value must be string")
...
u.email = 1
# ValueError: Value must be string
In the next post, we will try to decode how ORM(Object-Relational Mapping) take advantage of descriptors.
Top comments (2)
Great post! Where’s part 2?
Served.