Wednesday, September 23, 2009

Using Python Properties

The Python programming language is a loosely-typed language. What this essentially means is that any variable defined in a program can be assigned a value of any type. This also applies to attributes of classes. There is no need to specify the allowable types that a given attribute may hold. This offers the developer much flexibility when implementing an application. Even more flexibility may be added to a given class implementation by use of properties.

Python has a built-in property type that can be used to build complex attributes. By complex, I mean that the attributes can hold both data and behavior. This functionality can be made useful by imposing constraints on the attributes of a given instance because the behavior associated with the attribute is invoked when a value is set. This doesn't necessarily mean that the behavior is checking for the type of the value. That would defeat the purpose of a dynamically-typed language. It can, however, perform more complex testing such as checking the state of the value or making sure it falls within some range. The invoked behavior can also store and retrieve values from a non-standard location such as a database.

So why go to all this trouble? Why not just implement standard attributes and methods? That depends. The only reason to implement dynamic properties for Python instances is to provide a cleaner API for the client using the instances.

There are actually two methods in which to implement dynamic Python properties, both of which are illustrated below. The first, just overloads the __getattr__() and __setattr__() methods. These methods are invoked if the attribute requested does not exist in the standard location as a regular attribute. The benefit to this method is that these are the only methods that need to be implemented for attribute management. This means that any attributes can be set or retrieved on the instances. This can however lead to more work for these two methods because they are responsible for everything that might go wrong.

The second method, the property method, provides a better distribution of responsibilities. There is more work involved with the class implementation but is cleaner work that leads to a better client API.
#Example; Python properties.

#Simple person class.
class Person(object):

#The data for the instance attributes. This serves as
#an example that these attributes can be stored elsewhere.
data={"first_name":"", "last_name":""}

#Set an attribute.
def __setattr__(self, name, value):
self.data[name]=value

#Get an attribute.
def __getattr__(self, name):
return self.data[name]

#Simple person class.
class PropertyPerson(object):

#The data for the instance attributes. This serves as
#an example that these attributes can be stored elsewhere.
data={"first_name":"", "last_name":""}

#Set the first name.
def set_first_name(self, value):
self.data["first_name"]=value

#Set the last name.
def set_last_name(self, value):
self.data["last_name"]=value

#Get the first name.
def get_first_name(self):
return self.data["first_name"]

#Get the last name.
def get_last_name(self):
return self.data["last_name"]

#Create the properties using the previously
#defined instance methods.
first_name=property(fset=set_first_name, fget=get_first_name)
last_name=property(fset=set_last_name, fget=get_last_name)

#Main.
if __name__=="__main__":
#Create the test person instances.
person_obj=Person()
pperson_obj=PropertyPerson()

#Set some values without using properties.
person_obj.first_name="FirstName"
person_obj.last_name="LastName"

#Set some values using properties.
pperson_obj.first_name="FirstName"
pperson_obj.last_name="LastName"

#Display the attribute values of both instances.
print "Non-Property: %s %s"%(person_obj.first_name, person_obj.last_name)
print "\nProperty: %s %s"%(pperson_obj.first_name, pperson_obj.last_name)