29.6 Properties vs __getattr__: When Each Is Appropriate

In object-oriented Python, the mechanisms for controlling attribute access are powerful but distinct. Two primary tools for this are properties and the __getattr__ method. Understanding their fundamental differences—specifically, the distinction between data access via the descriptor protocol and via the lookup chain—is crucial for employing them correctly. Properties are a declarative tool built on Python’s descriptor protocol. They allow you to define getter, setter, and deleter methods for a specific, predefined attribute, transforming what looks like simple attribute access into method calls. This is ideal when you have a known attribute whose value you need to compute, validate, or otherwise control at the point of access.

29.5 When to Use Each: Design Guidance

Understanding the Core Concepts Before delving into design guidance, it’s crucial to have a precise understanding of the three mechanisms. A class in Python is a blueprint for creating objects. The methods defined within it typically operate on instances of that class. However, Python provides decorators to modify the behavior of these methods, creating @property, @classmethod, and @staticmethod. The @property decorator allows you to define a method that can be accessed like an attribute, enabling getter, setter, and deleter functionality without explicit method calls. A @classmethod receives the class itself (cls) as its first implicit argument, rather than an instance (self). This binds the method to the class, not to any particular instance. A @staticmethod receives no implicit first argument; it is essentially a function bundled into a class’s namespace for organizational purposes. It behaves like a regular function but is called on the class or an instance.

29.4 @staticmethod: Utility Functions Attached to a Class

The @staticmethod decorator in Python defines a method that is bound to a class rather than an instance of that class. Crucially, these methods receive no implicit first argument—neither an instance (self) nor a class (cls). This makes them identical to a regular function defined outside the class, except for one vital distinction: they are encapsulated within the class’s namespace. This encapsulation is the primary reason for their existence; it signals a strong logical connection between the function and the class, even if the function doesn’t need to access any class or instance state.

29.3 @classmethod: Alternate Constructors and Class-Level Operations

The @classmethod decorator in Python transforms a method into a class method. Unlike a standard instance method, which receives the instance (self) as its first argument, a class method receives the class itself (cls) as its first implicit argument. This fundamental shift in perspective unlocks two primary use cases: the creation of alternate constructors and the performance of operations that are bound to the class rather than any particular instance.

29.2 Setters and Deleters with @property

While the @property decorator allows you to define a getter, the full power of the property protocol is unlocked when you also define the corresponding setter and deleter methods. These methods are invoked when an attempt is made to assign a value to the property or delete it using the del statement, respectively. This mechanism transforms a simple attribute access into a controlled method call, enabling validation, computation, and side effects.

29.1 @property: Computed Attributes and Encapsulation

The @property decorator in Python provides an elegant mechanism for creating computed attributes and enforcing encapsulation within classes. It allows a method to be accessed like an attribute, blurring the line between simple data access and method invocation while maintaining a clean, object-oriented interface. This is a cornerstone of the “uniform access principle,” which states that clients should be able to access a class’s services without knowing whether they are implemented via storage or computation.

— joke —

...