Object Customization Techniques
Descriptor Protocol
Descriptors allow custom control over attribute access and modification.
class ValidatedAttribute:
def __init__(self, min_value=None, max_value=None):
self.min_value = min_value
self.max_value = max_value
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner):
return instance.__dict__.get(self.name)
def __set__(self, instance, value):
if self.min_value is not None and value < self.min_value:
raise ValueError(f"Value must be >= {self.min_value}")
if self.max_value is not None and value > self.max_value:
raise ValueError(f"Value must be <= {self.max_value}")
instance.__dict__[self.name] = value
class Person:
age = ValidatedAttribute(0, 120)
def __init__(self, name, age):
self.name = name
self.age = age
Property Decorators
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
@property
def fahrenheit(self):
return (self._celsius * 9/5) + 32
@fahrenheit.setter
def fahrenheit(self, value):
self._celsius = (value - 32) * 5/9
graph TD
A[Metaclass] --> B[Class Creation Control]
A --> C[Automatic Attribute Modification]
A --> D[Logging and Validation]
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class DatabaseConnection(metaclass=SingletonMeta):
def __init__(self, connection_string):
self.connection_string = connection_string
Context Managers
class ResourceManager:
def __init__(self, resource):
self.resource = resource
def __enter__(self):
print(f"Acquiring {self.resource}")
return self
def __exit__(self, exc_type, exc_value, traceback):
print(f"Releasing {self.resource}")
if exc_type:
print(f"An error occurred: {exc_type}")
## Usage
with ResourceManager("database connection") as rm:
## Perform operations
pass
Customization Techniques Comparison
| Technique |
Use Case |
Complexity |
Flexibility |
| Descriptors |
Attribute Control |
Medium |
High |
| Properties |
Computed Attributes |
Low |
Medium |
| Metaclasses |
Class Creation |
High |
Very High |
| Context Managers |
Resource Management |
Low |
Medium |
Advanced Customization Patterns
class ObservableList(list):
def __init__(self, *args):
super().__init__(*args)
self.observers = []
def add_observer(self, observer):
self.observers.append(observer)
def append(self, item):
super().append(item)
for observer in self.observers:
observer(self, item)
def print_observer(lst, item):
print(f"Item {item} added to list")
observable_list = ObservableList()
observable_list.add_observer(print_observer)
observable_list.append(42)
Best Practices
- Use customization techniques judiciously
- Prioritize readability and simplicity
- Understand the performance implications
At LabEx, we recommend mastering these techniques to create more flexible and powerful Python objects.