Computed Attributes, Using Property Objects

John Posner

This document describes computed attributes and introduces the easiest way to implement computed attributes in Python: using property objects.

Kinds of Attributes

In Python, an object's contents are accessed as attributes, using dot-notation. Some attributes provide access to stored data: integers, strings, lists, user- or system-defined objects, etc:

wgt.color
employee.first_name
team.positions

Other attributes provide access to functions (more correctly in this context: "methods"), which can be called with an argument list to execute code:

wgtlist.darkest_color()
employee.full_name(salutation=True, caps=False)
team.best_hitter(baseball.SLUGGING)

Python also provides a mechanism for blurring the distinction between these standard ways of using attributes. A computed attribute (or "managed attribute") looks like it directly accesses storage, but works like a function. That is, you code a computed attribute without parentheses or arguments, but accessing the attribute causes a function to be executed. Example:

The easiest way to implement a computed attribute is with an object of type property, introduced in Python 2.2. Depending on the way you access a property object, it dispatches a function to perform either a "get the value" operation or a "set the value" operation (or even "delete the attribute"). You specify the dispatch functions when you create the property object; it's up to you to ensure that the work they do makes sense. The following diagram illustrates how a property works.

py-props.png

Creating a Computed Attribute

Here's a scenario that calls for a computed attribute that implements "get the value" and "set the value" operations ... To model your company's widgets, which come in a variety of colors, you use a class named Widget, with instance attribute color:

class Widget(object):
    def __init__(self, arg=None):
        if arg:
            self.color = arg
        else:
            self.color = (0,0,0)

In the past, all Widget colors were specified as (R,G,B) tuples, so programs could make simple references to, and assignments to, the color attribute. But now, there's a business need to support colors specified as #rrggbb strings, also. Management decides that a Widget object must store the color in its originally specified format (string or tuple), but must always report the color as an (R,G,B) tuple. Moreover, existing programs using the color attribute must continue to work, without modification.

These requirements can be handled by turning color into a computed attribute:

The following code implements get_color and set_color as methods of the Widget class. Alternatively (but less object-orientedly), they could be implemented as functions outside the class definition.

class Widget(object):
    def __init__(self, arg=None):
        if arg:
            self.color_data = arg
        else:
            self.color_data = (0,0,0)

    def get_color(self):
        if type(self.color_data) is tuple:
            return self.color_data
        else:
            return str_to_tuple(self.color_data)

    def set_color(self, arg):
        self.color_data = arg

    # create property object to dispatch 'get' and 'set' methods
    # NOTE: "color" is a class attribute, not an instance attribute
    color = property(get_color, set_color)

The get_color method uses a utility function, str_to_tuple:

def str_to_tuple(color_string):
    return (int(color_string[1:3], 16),   # red
            int(color_string[3:5], 16),   # green
            int(color_string[5:], 16))    # blue

Now, when a program makes a reference to the attribute color through a Widget instance wgt, the property object dispatches get_color, which retrieves the color_data value and returns an (R,G,B) tuple. Similarly, when a program makes an assignment to the attribute color:

wgt.color = "#ff80ff"

.... the property object dispatches set_color with the argument "#ff80ff", causing the value to be stored in color_data.

Some coding details:

References

We haven't finished the story on property objects -- for example, we haven't discussed how to implement a computed attribute that handles the statement del wgt.color. But if you've gotten this far, it probably makes sense to switch to the official property documentation:

Or see a revision of the official documentation, at AlternativeDescriptionOfProperty.

(Don't get fooled -- as I was! -- by the classification of property in the documentation as a built-in function. property is a built-in data type, like int. In Python, data types are directly callable, and so look like functions.)

ComputedAttributesUsingPropertyObjects (last edited 2019-12-15 08:11:04 by FrancesHocutt)

Unable to edit the page? See the FrontPage for instructions.