In this blog, we have some fun creating a class that implements the mapping protocol. The class also provides a simple example of the techniques of lazy initialization and memoization.
Let's say we have a configuration file, planets.conf that lists standard attributes for each planet in the solar system.
planets.conf
[Mercury] orbit = 57910000 diameter = 4880 mass = 3.30e23 [Venus] orbit = 108200000 diameter = 12103.6 mass = 4.869e24 [Earth] orbit = 149600000 diameter = 12756.3 mass = 5.972e24 satellites = Moon [Mars] orbit = 227940000 diameter = 6794 mass = 6.4219e23 satellites = Phobos Deimos . . .
Let's now create a structure-like class, called Planet, that encapsulates the attributes of planet name, orbit, diameter, mass, and satellites.
class Planet: def __init__(self, name, orbit, diameter, mass, satellites = []): self._name = name self._orbit = orbit self._diameter = diameter self._mass = mass self._satellites = satellites def __str__(self): return '%s%s\torbit: %.2f%s\tdiameter: %.2f%s\tmass: %g%s' + '\tsatellites: %s%s' \ % (self._name, os.linesep, \ self._orbit, os.linesep, \ self._diameter, os.linesep, \ self._mass, os.linesep, \ ', '.join(self._satellites), os.linesep) @property def name(self): return self._name @property def orbit(self): return self._orbit @property def diameter(self): return self._diameter @property def mass(self): return self._mass @property def satellites(self): return self._satellites
Next, we create a dictionary-like class, called Planets, that manages the creation of each Planet object.
class Planets: _NUM_PLANETS = 9 _PLANET_NAMES = ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune', 'Pluto'] def __init__(self): self._planets = {} self._parser = SafeConfigParser() self._parser.read('planets.conf') def __len__(self): return self._NUM_PLANETS def __getitem__(self, key): if key in self._planets: return self._planets[key] if not self._parser.has_section(key): raise KeyError(key) orbit = self._parser.getfloat(key, 'orbit') diameter = self._parser.getfloat(key, 'diameter') mass = self._parser.getfloat(key, 'mass') satellites = [] if self._parser.has_option(key, 'satellites'): satellites = self._parser.get(key, 'satellites').split() self._planets[key] = Planet(key, orbit, diameter, mass, satellites) return self._planets[key]
In particular, if a user performs the following operations:
p = Planets() earth = p['Earth']
then Planets will read the statistics for planet Earth from planets.conf, create a new Planet that represents Earth, and add this planet to its private dictionary of planets (self._planets). Thus, the operation of constructing a planet from a configuration file is only performed if that planet is accessed. On subsequent accesses of that planet, Planets returns the memoized Planet object.
Note that, for fun, we have Planets implement the __len__ and __getitem__ magic methods, thereby fulfilling the protocol of an immutable mapping.
The entire program is listed below.
lazy_planets.py
#!/usr/bin/env python from ConfigParser import SafeConfigParser import os __metaclass__ = type # new-style classes class Planet: def __init__(self, name, orbit, diameter, mass, satellites = []): self._name = name self._orbit = orbit self._diameter = diameter self._mass = mass self._satellites = satellites def __str__(self): return '%s%s\torbit: %.2f%s\tdiameter: %.2f%s\tmass: %g%s' + '\tsatellites: %s%s' \ % (self._name, os.linesep, \ self._orbit, os.linesep, \ self._diameter, os.linesep, \ self._mass, os.linesep, \ ', '.join(self._satellites), os.linesep) @property def name(self): return self._name @property def orbit(self): return self._orbit @property def diameter(self): return self._diameter @property def mass(self): return self._mass @property def satellites(self): return self._satellites class Planets: _NUM_PLANETS = 9 _PLANET_NAMES = ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune', 'Pluto'] def __init__(self): self._planets = {} self._parser = SafeConfigParser() self._parser.read('planets.conf') def __len__(self): return self._NUM_PLANETS def __getitem__(self, key): if key in self._planets: return self._planets[key] if not self._parser.has_section(key): raise KeyError(key) orbit = self._parser.getfloat(key, 'orbit') diameter = self._parser.getfloat(key, 'diameter') mass = self._parser.getfloat(key, 'mass') satellites = [] if self._parser.has_option(key, 'satellites'): satellites = self._parser.get(key, 'satellites').split() self._planets[key] = Planet(key, orbit, diameter, mass, satellites) return self._planets[key] if __name__ == '__main__': p = Planets() print('Number of planets: %d' % (len(p), )) earth = p['Earth'] print(earth) mars = p['Mars'] print('The moons of mars are: %s' % (' ,'.join(mars.satellites), )) try: ceres = p['Ceres'] except KeyError, e: print 'Ceres is not a planet!'Here is the output of the program:
$ ./lazy_planets.py Number of planets: 9 Earth orbit: 149600000.00 diameter: 12756.30 mass: 5.972e+24 satellites: Moon The moons of mars are: Phobos ,Deimos Ceres is not a planet!
No comments:
Post a Comment