1. Customizing Attribute Access
Descriptors aren't the only tool that can be used to change an attribute's behavior - let's take a look at another technique used to customize attribute access!
2. AttributeError
The Student class defined here has two parameters; student_name and major. If we create an object of Student and attempt to access an attribute that doesn't exist, such as residence_hall, we'll get an AttributeError. We can alter this behavior using a magic method.
3. __getattr__()
The getattr method is a magic method executed when an attempt to reference any attribute outside an object's namespace is made. While descriptors are built for a single attribute, getattr is executed when ANY attribute is retrieved.
An object's namespace is a collection of attributes that are associated with that object.
Like other magic methods, getattr is defined using double underscores, and is not directly called. getattr takes a single parameter "name", a string with the name of the attribute being accessed.
Rather than raising an AttributeError, getattr allows for us to implement custom functionality when trying to access an attribute outside an object's namespace.
4. Resolving the AttributeError
Here, we've implemented getattr in the Student class. Instead of throwing an AttributeError when attempting to access residence_hall, a message is printed, suggesting next steps.
5. __setattr__()
In addition to getattr, setattr is another magic method used to customize attribute behavior.
setattr is called when an existing attribute is set or updated, or a new one is created. This includes when an attribute is created within a constructor.
setattr takes the name of an attribute as a string, along with the value that attribute should take.
Within the setattr method, the dict attribute is typically leveraged to set and store other attributes after some sort of validation or transformation.
dict is a dictionary that stores all attributes of an object using key-value pairs. dict can be used to retrieve and store data, just like with a normal dictionary.
Using setattr unlocks powerful control over object attributes; now, all attribute values can be validated or even transformed before being set.
Let's take a look at an example!
6. Customizing attribute storage
In the Student class, we've added a setattr method that will be called when either a new or existing attribute is set.
setattr ensures that the values of all attributes are strings, and prints a message before adding the attribute-value pair to the object's namespace using dict.
If a value is not a string, an exception is raised, noting an unexpected data type.
7. __setattr__ in action
Now, when the residence_hall attribute is set, the setattr method is called, validating the type of the value being stored, before printing a message and storing "Honor's College South".
Attempting to set the student_id to an integer value, raises an exception, noting an unexpected data type.
8. Using __getattr__ and __setattr__ together
getattr and setattr don't have to be used independently.
Now, when an attribute outside an object's namespace is accessed, that attribute is stored with a value of None, and None is also returned. This creates a placeholder value, rather than raising an AttributeError.
If the value of an attribute is None, the setattr method prints a message, before storing the attribute-value pair using dict, regardless of value's type.
9. Let's practice!
It's your turn to practice using getattr and setattr to customize attribute behavior!