Attributes, Properties and Descriptors

When we reference an attribute of an object with something like someObject.someAttr, Python uses several special methods to get the someAttr attribute of the object.

In the simplest case, attributes are simply instance variables. But that’s not the whole story. To see how we can control the meaning of attributes, we have to emphasize a distinction:

  • An attribute is a name that appears after an object name. This is the syntactic construct. For example, someObj.name.
  • An instance variable is an item in the internal __dict__ of an object.

The default semantics of an attribute reference is to provide access to the instance variable. When we say someObj.name, the default behavior is effectively someObj.__dict__['name'].

This is not the only meaning an attribute name can have.

In Semantics of Attributes we’ll look at the various ways we can control what an attribute name means.

The easiest technique for controlling the meaning of an attribute name is to define a property. We’ll look at this in Properties.

The most sophisticated technique is to define a descriptor. We’ll look at this in Descriptors.

The most flexible technique is to override the special method names and take direct control over attribute processing. We’ll look at this in Attribute Access Exercises.

Semantics of Attributes

Fundamentally, an object encapsulates data and processing via it’s instance variables and method functions. Because of this encapsulation, we can think of a class definition as providing both an interface definition and an implementation that supports the defined interface.

In some languages, the attributes are the instance variables; an attribute can name expose the private state of the object. Consequently, some languages suggest that attributes should not be part of the interface. Further, some languages (C, C#, Java are examples) provide syntax to distinguish the public interface from the private implementation.

In Python, this distinction between interface and implementation is not heavily emphasized in the syntax, since it can often lead to wordy, complex programs. Most well-designed classes, however, tend to have a set of interface methods that form the interface for collaborating with objects of that class.

In Python, the method names and instance variables which begin with _ are treated as part of the private implementation of the class. These names aren’t shown in the help() function, for example. The remaining elements (without a leading _) form the public interface.

Encapsulation There are two commonly-used design patterns for encapsulating an object’s instance variables.

  • Getters and Setters. This design pattern can insulate each instance variable with some method functions to get and set the value of that instance variable.

    When we do this, each access to an attribute of the object is via an explicit function: anObject.setPrice( someValue ) and anObject.getPrice().

  • Attributes. We can insulate an instance variable with more sophisticated attribute access. In the case of Python, we have several techniques for handling attribute access.

Python Attributes. In Python, the attributes do not have to be instance variables.

  • Properties. We can bind getter, setter (and deleter) functions with an attribute name, using the built-in property() function or @property decorator. When we do this, each reference to an attribute has the syntax of direct access to an instance variable, but it invokes the given method function.

    We’ll look at this in Properties.

  • Descriptors. We can implement getter, setter (and deleter) functions into a separate descriptor class. We then create an instance of this class as the object named by an attribute. When we do this, each reference to an attribute has the syntax of direct access to an instance variable, but it invokes a method function of the Descriptor object.

    We’ll look at this in Descriptors.

  • Special Method Names. We can override definitions for several special method names. There are several methods which plug into the standard algorithm. A fourth method, __getattribute__(), allows you to change attribute access in a fundamental way.

    We’ll look at this in Attribute Access Exercises.

    Warning

    Caution

    Changing attribute access in radical ways can interfere with how people understand the operation of your classes and objects. The default assumption is that an attribute is an instance variable. While we can fundamentally alter the meaning of a Python attribute, we need to be cautious about violating the default assumptions of people reading our software.

Properties

The property() function gives us a handy way to implement an attribute that is more than a simple reference to an instance variable.

Through the property function, we can assign a simple attribute name to parameter-less getter and setter method functions.

This allows us to create programs that look like the following example.

>>> oven= Temperature()
>>> oven.farenheit= 450
>>> oven.celsius
232.22222222222223
>>> oven.celsius= 175
>>> oven.farenheit
347.0

In this example, we set one attribute and the value of another attribute changes to mirror it precisely. We can do this by defining some method functions and binding them to attribute names.

Property Design Pattern. The Property design pattern has a number of method functions which are bound together with a single property name. The method functions can include any combination of a getter, a setter and a deleter.

To create a property, we define the instance variable and one or more method functions. This is identical with the Getter and Setter design pattern. To make a property, we provide these method functions to the property() function to bind the various methods to an attribute name.

Here’s the definition of the property() function.

property(fget[, fset, fdel, doc]) → property attribute

This binds the given method functions into a property definition. Usually the result value is assigned to an attribute of a class.

The fget parameter must be a getter function, of the form function(self)->value.

The fset parameter must be a setter function, of the form function(self,value).

The fdel parameter must be a deleter function, of the form function(self). This can be used to delete the attribute, and is evaluated in response to del object.attribute.

The doc parameter becomes the docstring for the attribute. If this is not provided the docstring from the getter function becomes the docstring for the property.

class SomeClass( object ):
    def getThis( self ):
        return self._hidden_variable * 2
    def setThis( self, value ):
        self._hidden_variable = float(value) / 2
    this= property( getThis, setThis )

This creates a property, named this: defined by two method functions getThis() and setThis(). The functions do a fairly silly calculation, but this shows how an attribute can embody a calculation.

The property function can also be used as a decorator. We’ll look at decorators in detail in Decorators.

Here is a quick sample of the using the the property() with the decorato syntax instead of the function syntax.

class SomeClass( object ):
    @property
    def this( self ):
        return self._hidden_variable * 2
    @this.setter
    def this( self, value ):
        self._hidden_variable = float(value) / 2

Property Example. The following example shows a class definition with four method functions that are used to define two properties.

property.py

class Temperature( object ):
    def fget( self ):
        return self.celsius * 9 / 5 + 32
    def fset( self, value ):
        self.celsius= (float(value)-32) * 5 / 9
    farenheit= property( fget, fset )
    def cset( self, value ):
        self.cTemp= float(value)
    def cget( self ):
        return self.cTemp
    celsius= property( cget, cset, doc="Celsius temperature" )
  1. We create the farenheit property from the fget() and fset() method functions. When we use the farenheit attribute on the left side of an assignment statement, Python will use the setter method. When we use this attribute in an expression, Python will use the getter method. We don’t show a deleter method; it would be used when the attribute is used in a del statement.

  2. We create the celsius property from the cget() and cset() method functions. When we use the celsius attribute on the left side of an assignment statement, Python will use the setter method. When we use this attribute in an expression, Python will use the getter method.

    The doc string provided for the celsius attribute is available as Temperature.celsius.__doc__.

Descriptors

A Descriptor is a class which provides the detailed get, set and delete control over an attribute of another object. This allows you to define attributes which are fairly complex objects in their own right. The idea is that we can use simple attribute references in a program, but those simple references are actually method functions of a descriptor object.

This allows us to create programs that look like the following example.

>>> oven= Temperature()
>>> oven.farenheit= 450
>>> oven.celsius
232.22222222222223
>>> oven.celsius= 175
>>> oven.farenheit
347.0

In this example, we set one attribute and the value of another attribute changes to mirror it precisely.

A common use for descriptors is in an object-oriented database (or an object-relational mapping). In a database context, getting an attribute value may require fetching data objects from the file system; this may involve creating and executing a query in a database.

This kind of “related-item fetch” operation will be shared widely among the persistent classes in an application. Rather than attempt to manage this shared functionality via inheritance of method functions, it’s simpler to split it into a separate Descriptor class and use this descriptor to manage the access of related objects.

Descriptor Design Pattern. The Descriptor design pattern has two parts: an Owner and an attribute Descriptor. The Owner is usually a relatively complex object that uses one or more Descriptors for its attributes. A Descriptor class defines get, set and delete methods for a class of attributes of the Owner. Each Descriptor object manages a specific attribute.

Note that Descriptors will tend to be reusable, generic classes of attributes. The Owner can have multiple instances of each Descriptor class to manage attributes with similar behaviors. Each Descriptor object is a unique instance of a Descriptor class, bound to a distinct attribute name when the Owner class is defined.

To be recognized as a Descriptor, a class must implement some combination of the following three methods.

Descriptor.__get__(self, instance, owner) → object

The instance parameter is the self variable of object being accessed. The owner parameter is the owning class object. This method of the descriptor must return this attribute’s value.

If this descriptor implements a class level variable, the instance parameter can be ignored; the instance is irrelevant and may be None. The owner parameter refers to the class.

Descriptor.__set__(self, instance, value)
The instance argument is the self variable of the object being updated. This method of the descriptor must set this attribute’s value.
Descriptor.__delete__(self, instance)
The instance argument is the self variable of the object being accessed. This method of the descriptor must delete this attribute’s value.

Sometimes, a Descriptor class will also need an __init__() method function to initialize the descriptor’s internal state. Less commonly, the descriptor may also need __str__() or __repr__() method functions to display the instance variable correctly.

You must also make a design decision when defining a descriptor. You must determine where the underlying instance variable is contained. You have two choices.

  • The Descriptor object has the instance variable. In this case, the descriptor object’s self variable has the instance variable.

    This is common for attributes that can be updated.

  • The Owner object contains the instance variable. In this case, the descriptor object must use the instance parameter to reference a value in the owning object.

    This is common for attributes that are “get-only”.

Descriptor Example. Here’s a simple example of an object with two attributes defined by descriptors. One descriptor (Celsius) contains it’s own value. The other desriptor (Farenheit), depends on the Celsius value, showing how attributes can be “linked” so that a change to one directly changes the other.

descriptor.py

class Celsius( object ):
    """Fundamental Temperature Descriptor."""
    def __init__( self, value=0.0 ):
        self.value= float(value)
    def __get__( self, instance, owner ):
        return self.value
    def __set__( self, instance, value ):
        self.value= float(value)

class Farenheit( object ):
    """Requires that the owner have a ``celsius`` attribute."""
    def __get__( self, instance, owner ):
        return instance.celsius * 9 / 5 + 32
    def __set__( self, instance, value ):
        instance.celsius= (float(value)-32) * 5 / 9

class Temperature( object ):
    celsius= Celsius()
    farenheit= Farenheit()
  1. We’ve defined a Celsius descriptor. The Celsius descriptor has an __init__() method which defines the attribute’s value. The Celsius descriptor implements the __get__() method to return the current value of the attribute, and a __set__() method to change the value of this attribute.

  2. The Farenheit descriptor implements a number of conversions based on the value of the celsius attribute. The __get__() method converts the internal value from Celsius to Farenheit. The __set__() method converts the supplied value (in Farenheit) to Celsius.

    In a way, the Farenheit descriptor is an “observer” of the owning objects’s celsius attribute.

  3. The owner class, Temperature has two attributes, both of which are managed by descriptors. One attribute, celsius, uses an instance of the Celsius descriptor. The other attribute, farenheit, uses an instance of the Fareheit descriptor. When we use one of these attributes in an assignment statement, the descriptor’s __set__() method is used. When we use one of these attributes in an expression, the descriptor’s __get__() method is used. We didn’t show a __delete__() method; this would be used when the attribute is used in a del statement.

Let’s look at what happens when we set an attribute value, for example, using oven.farenheit= 450 . In this case, the farenheit attribute is a Descriptor with a __set__() method. This __set__() method is evaluated with instance set to the object which is being modified (the oven variable) and owner set to the Temperature class. The __set__() method computes the celsius value, and provides that to the celsius attribute of the instance. The Celsius descriptor simply saves the value.

When we get an attribute value, for example, using oven.celsius, the following happens. Since celsius is a Descriptor with a __get__() method, this method is evaluated, and returns the celsius temperature.

Attribute Handling Special Method Names

Fundamentally, attribute access works through a few special method names. Python has a default approach: it checks the object for an instance variable that has the attribute’s name before using these attribute handling methods.

Because Python uses these methods when an attribute is not already an instance variable, you can easily create infinite recursion. This can happen if you write self.someAttr inside the body of a __getattr__() or __setattr__() method and the attribute is not in the object’s __dict__, Python will use the __getattr__() or __setattr__() method to resolve the name. Oops.

Within __getattr__() and __setattr__(), you have to use the internal __dict__ explicitly.

These are the low-level attribute access methods.

__getattr__(self, name) → value
Returns a value for an attibute when the name is not an instance attribute nor is it found in any of the parent classes. name is the attribute name. This method returns the attribute value or raises an AttributeError exception.
__setattr__(self, name, value)

Assigns a value to an attribute. name is the attribute name, value is the value to assign to it.

Note that if you naively do self.name= value in this method, you will have an infinite recursion of __setattr__() calls.

To access the internal dictionary of attributes, __dict__, you have to use the following: self.__dict__[name] = value.

__delattr__(self, name)
Delete the named attribute from the object. name is the attribute name.
__getattribute__(self, name) → value

Low-level access to a named attribute. If you provide this, it replaces the default approach of searching for an attribute and then using __getattr__() if the named attribute isn’t an instance variable of the class.

If you want to extend the default approach, you must explicitly evaluate the superclass __getattribute__() method with super( Class,self ).__getattribute__( name ). This only works for classes which are derived from object.

Attribute Access Exercises

  1. Rework Previous Exercises. Refer to exercises for previous chapters (Class Definition Exercises, Advanced Class Definition Exercises, Design Pattern Exercises, Special Method Name Exercises). Rework these exercises to manage attributes with getters and setters. Use the property function to bind a pair of getter and setter functions to an attribute name. The following examples show the “before” and “after” of this kind of transformation.

    class SomeClass( object ):
        def __init__( self, someValue ):
            self.myValue= someValue
    

    When we introduce the getter and setter method functions, we should also rename the original attribute to make it private. When we define the property, we can use the original attribute’s name. In effect, this set of transformations leaves the class interface unchanged. We have added the ability to do additional processing around attribute get and set operations.

    class SomeClass( object ):
        def __init__( self, someValue ):
            self._myValue= someValue
        def getMyValue( self ):
            return self._myValue
        def setMyvalue( self, someValue ):
            self._myValue= someValue
        myValue= property( getMyValue, setMyValue )
    

    The class interface should not change when you replace an attibute with a property. The original unit tests should still work perfectly.

  2. Rework Previous Exercises. Refer to exercises for previous chapters (Class Definition Exercises, Advanced Class Definition Exercises, Design Pattern Exercises, Special Method Name Exercises). Rework these exercises to manage attributes with Descriptors. Define a Descriptor class with __get__() and __set__() methods for an attribute. Replace the attribute with an instance of the Descriptor.

    When we introduce a descriptor, our class should look something like the following.

    class ValueDescr( object ):
        def __set__( self, instance, value ):
            instance.value= value
        def __get__( self, instance, owner ):
            return instance.value
    
    class SomeClass( object ):
        def __init__( self, someValue ):
            self.myValue= ValueDescr()
    

    The class interface should not change when you replace an attibute with a descriptor. The original unit tests should still work perfectly.

  3. Tradeoffs and Design Decisions. What is the advantage of Python’s preference for referring to attributes directly instead of through getter and setter method functions?

    What is the advantage of having an attribute bound to a property or descriptor instead of an instance variable?

    What are the potential problems with the indirection created by properties or descriptors?

Table Of Contents

Previous topic

Creating or Extending Data Types

Next topic

Decorators

This Page