While Python offers flexible
container data structures, such as lists and dictionaries, it is occasionally helpful to create our own. Suppose, for example, that we wanted to create a data structure with two dictionaries: one for key-value pairs where len(key) is even, and one for key-value pairs where len(key) is odd. We would like to allow the Python builtins, such as str(), len(), container[key], etc, to access our objects in a meaningful way, and Python lets us do this via container-emulation methods:
import sys
# -- The Container class
class Container:
# Instance variables:
# self.even is a dictionary which stores key-value pairs
# where the key has an even length
# self.odd is a dictionary which stores key-value pairs
# where the key has an odd length
# Initialize new Container objects
def __init__(self):
self.even = dict(); self.odd = dict()
# __len__() lets you use len() to evaluate this object
def __len__(self):
return len(self.even)+len(self.odd)
# __repr__() lets you use str() to evaluate this object
def __repr__(self):
return str(self.even)+"\n"+str(self.odd)
# __getitem__ allows evaluation of container[key]
def __getitem__(self, key):
if len(key)%2 == 0: return self.even[key]
else: return self.odd[key]
# __setitem__ allows assignment to container[key]
def __setitem__(self, key, value):
if len(key)%2 == 0: self.even[key] = value
else: self.odd[key] = value
# __delitem__ lets you use del container[key] to remove keys
def __delitem__(self, key):
if len(key)%2 == 0: del self.even[key]
else: del self.odd[key]
# __contains__ lets you use "key in container" boolean expressions
def __contains__(self, key):
return (key in self.even or key in self.odd)
# __iter__ lets you iterate through the items via "for x in container"
def __iter__(self):
ret = []
ret.extend(self.even.keys())
ret.extend(self.odd.keys())
return ret.__iter__()
# -- The main function (diagnostic)
def main(args):
# __init__
c = Container()
# __setitem__
print "Adding key-value pairs\n"
c["Laverne"] = "Shirley"
c["Ozzie"] = "Harriet"
c["Abbott"] = "Costello"
c["Carl"] = "Steve"
# __len__
print "The length of our container is", len(c), "\n"
# __repr__
print "One string representation is:\n", str(c), "\n"
# __getitem__
print "The partner of Abbott is", c["Abbott"], "\n"
# __delitem__
del c["Laverne"]
print "We have just deleted the Laverne:Shirley key-value pair"
print "The new length is", len(c)
print "Now, the container looks like\n", str(c), "\n"
# __contains__
print "Is the key 'Batman' in our container?"
if "Batman" in c: print c["Batman"]
else: print "Sorry, Boy Wonder"
print "Is the key 'Ozzie' in our container?"
if "Ozzie" in c: print "Yes, and it is", c["Ozzie"], "\n"
else: print "No Ozzies here", "\n"
# __iter__
print "Let's iterate through all available keys and their values"
for x in c: print x, "\t", c[x]
# -- The following code executes upon program invocation
if __name__ == "__main__": main(sys.argv) What this gives you is the freedom to define your own container data structures, and then use them just like you would use Python-supplied containers. Pretty snazzy!
The trick here is that you can define these functions any way you want. For example, your __repr__ method
could always return "fish" regardless of the values of the object's instance variables - but that would be counter-intuitive, because programmers generally expect str() to return a string value that represents, at least in part, the internal state of the object. So beware!
Also, the __iter__ method I provided doesn't really build an iterator object - it just compiles a list of keys, then borrows the iterator from that. For more information on iterators and generators, see van Rossum's reference manual.