__get__()
, __set__()
or __delete__()
methods and optionally __set_name__()
method. They allow objects to customize the attribute's/variable's lookup, assignment and deletion.There are two types of Descriptors.
__set__()
or __delete__()
methods defined.__get__()
method defined.## Example: Use Data descriptors to create class attributes with
# their own functionality
class MyDescriptor:
# when a 'MyDescriptor' object is created inside a class this
# function is called first, it records the class name for later reference
def __set_name__(self, obj, name):
# here 'obj' is that class ('MyClass' in our case)
# 'self' is our 'MyDescriptor' object
# 'private_name' is our internal access name
# using '"_" + name' for avoiding name collisions
self.private_name = "_" + name
# When a attribute is looked up (using '.' operator), this method is called
def __get__(self, obj, objtype=None):
# fetch 'private_name' from 'MyClass' instance using 'getattr()'
value = getattr(obj, self.private_name)
print(f"{self.private_name} was accessed")
return value
# When a attribute is altered (using '=' operator), this method is called
def __set__(self, obj, value):
# here we can decide what is valid value for 'my_var1'
if self.private_name == "_my_var1":
if value % 2 == 0:
raise AttributeError("Not a valid number, require odd number")
# decide what is valid value for 'my_var2'
elif self.private_name == "_my_var2":
if value % 2 != 0:
raise AttributeError("Not a valid number, require even number")
# set value to the variable 'private_name' of 'MyClass' instance
setattr(obj, self.private_name, value)
print(f"{self.private_name} was altered")
# define a class that will contain our descriptor objects
class MyClass:
# initialize our descriptor object
my_var1 = MyDescriptor()
# create another attribute
my_var2 = MyDescriptor()
def __init__(self, var1, var2, var3, var4):
# calls '__set__()' method of our descriptor to assign the value
# here we are assigning 'var1' and 'var2' values to our descriptors
# which are 'my_var1' and 'my_var2'
self.my_var1 = var1
self.my_var2 = var2
# normal variables
self.my_var3 = var3
self.my_var4 = var4
## Create a instance of MyClass
my_instance = MyClass(11, 12, 30, 40) # _my_var1 was altered \
# _my_var2 was altered
# Notice: The print message we set in '__set__()' method is printed
## Check attribute names of our instance
print(my_instance.__dict__) # {'_my_var1': 11, '_my_var2': 12, \
# 'my_var3': 30, 'my_var4': 40}
## Accessing the variables using the '.' operator
# calls the '__get__()' method of our descriptor to get its value
print(my_instance.my_var1) # _my_var1 was accessed \
# 11
print(my_instance.my_var2) # _my_var2 was accessed \
# 12
## Calling normal variables
print(my_instance.my_var3) # 30
print(my_instance.my_var4) # 40
# Notice: 'my_var3', 'my_var4' show normal behaviour without any print message
## Now trying to input invalid values according to out descriptors
my_instance.my_var1 = 42 # AttributeError: Not a valid value, require \
# odd number
my_instance.my_var2 = 41 # AttributeError: Not a valid value, require \
# even number
# Notice: 'AttributeError' is raised due the values didn't match,
# try a valid value