Python magic methods to get/set attributes

In Python, you can define special methods called “magic methods” or “dunder methods” to customize how attributes are accessed and modified in your objects. Here are the magic methods that you can use to get and set attributes in Python:
__getattr__(self, name)
: This method is called when an attempt is made to access an attribute that does not exist in the object's namespace. It takes a single argumentname
, which is the name of the attribute being accessed. You can use this method to dynamically generate attributes or raise an AttributeError if the attribute does not exist.__getattribute__(self, name)
: This method is called when an attempt is made to access any attribute of the object. It takes a single argumentname
, which is the name of the attribute being accessed. You can use this method to intercept attribute access and customize how the attribute is retrieved.__setattr__(self, name, value)
: This method is called when an attempt is made to set the value of an attribute of the object. It takes two argumentsname
andvalue
, which are the name and value of the attribute being set. You can use this method to intercept attribute setting and customize how the attribute is stored.__delattr__(self, name)
: This method is called when an attempt is made to delete an attribute of the object. It takes a single argumentname
, which is the name of the attribute being deleted. You can use this method to intercept attribute deletion and customize how the attribute is removed.
Here’s an example that demonstrates how to use these magic methods:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __getattr__(self, name):
if name == "email":
return f"{self.name.lower()}@example.com"
else:
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
def __setattr__(self, name, value):
if name == "age":
if value < 0:
raise ValueError("Age must be a positive integer.")
object.__setattr__(self, name, value)
def __delattr__(self, name):
if name == "email":
raise AttributeError("Email attribute cannot be deleted.")
else:
object.__delattr__(self, name)
person = Person("Alice", 30)
# Access the 'name' and 'age' attributes of the person object
print(person.name) # Output: "Alice"
print(person.age) # Output: 30
# Access the 'email' attribute of the person object
print(person.email) # Output: "alice@example.com"
# Attempt to access a non-existing attribute
try:
print(person.address)
except AttributeError as e:
print(str(e)) # Output: "'Person' object has no attribute 'address'"
# Attempt to set the 'age' attribute to a negative value
try:
person.age = -10
except ValueError as e:
print(str(e)) # Output: "Age must be a positive integer."
# Attempt to delete the 'email' attribute
try:
del person.email
except AttributeError as e:
print(str(e)) # Output: "Email attribute cannot be deleted."
In this example, the Person
class defines __getattr__
, __setattr__
, and __delattr__
methods to customize attribute access and modification. The __getattr__
method dynamically generates an email
attribute based on the name
attribute, and raises an AttributeError if the attribute being accessed does
What is the differences between __getattr__ and __getattribute__
The two magic methods __getattr__
and __getattribute__
in Python are used to customize how attribute access is handled in objects. However, they have some key differences:
__getattr__(self, name)
: This method is called only when an attempt is made to access an attribute that does not exist in the object's namespace. It takes a single argumentname
, which is the name of the attribute being accessed. You can use this method to dynamically generate attributes or raise an AttributeError if the attribute does not exist.__getattribute__(self, name)
: This method is called for every attribute access on the object, regardless of whether the attribute exists in the object's namespace or not. It takes a single argumentname
, which is the name of the attribute being accessed. You can use this method to intercept attribute access and customize how the attribute is retrieved.
Here are some key differences between __getattr__
and __getattribute__
:
- Handling of existing attributes:
__getattr__
is only called when an attribute is not found in the object's namespace, whereas__getattribute__
is called for every attribute access. This means that if you define__getattribute__
in a class, it will intercept all attribute accesses, even if the attribute exists in the object's namespace. - Recursion: Since
__getattribute__
is called for every attribute access, it can potentially cause infinite recursion if you try to access an attribute within the__getattribute__
method itself. To avoid this, you should useobject.__getattribute__(self, name)
to retrieve the attribute within the method. - Handling of errors: If
__getattr__
raises an AttributeError, the interpreter will fall back to using the default attribute lookup behavior. However, if__getattribute__
raises an AttributeError, the interpreter will raise the exception directly. - Performance: Since
__getattribute__
is called for every attribute access, it can be less performant than__getattr__
if you only need to intercept attribute accesses for non-existent attributes.
Here’s an example that demonstrates the difference between __getattr__
and __getattribute__
:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __getattr__(self, name):
if name == "email":
return f"{self.name.lower()}@example.com"
else:
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
def __getattribute__(self, name):
print(f"Getting attribute {name}")
return object.__getattribute__(self, name)
person = Person("Alice", 30)
# Access the 'name' and 'age' attributes of the person object
print(person.name) # Output: "Alice"
print(person.age) # Output: 30
# Access the 'email' attribute of the person object
print(person.email) # Output: "alice@example.com"
# Attempt to access a non-existing attribute
try:
print(person.address)
except AttributeError as e:
print(str(e)) # Output: "'Person' object has no attribute 'address'"
n this example, the Person
class defines both __getattr__
and __getattribute__
methods. When the person.name
and person.age
attributes are accessed, __getattribute__
is called, and when the person.email
and person.address
attributes are accessed, __getattr__
is called. You can see that __getattribute__
is called for every attribute access, even if the attribute exists in the object's namespace. This is why it is important to use object.__getattribute__(self, name)
to retrieve the attribute within the method, as shown in this example.
When the person.email
attribute is accessed, __getattr__
is called because the attribute does not exist in the object's namespace. When the person.address
attribute is accessed, __getattr__
is also called, and an AttributeError is raised because the attribute does not exist in the object's namespace. Note that the error message is different from the one raised by __getattr__
in this case, because __getattribute__
raises the AttributeError directly.
In general, you should use __getattr__
when you want to customize the behavior of attribute access for non-existent attributes, and use __getattribute__
when you want to intercept all attribute accesses and customize how the attributes are retrieved. However, you should be careful when using __getattribute__
to avoid infinite recursion and performance issues.