Overspecialized Constructors

I want to discuss a simple mistake which recently caused me a disproportionate amount of trouble. I believe I violated a simple rule which might be formulated as “do not create overly specialized constructors in languages which lack constructor overloading“.

The Problem

When I was building my Python KenKen solver, I created a class to manage puzzle statements:

class Puzzle(object):
	lut = {'!':Assert, '+':Sum, '-':Diff, '*':Prod, '/':Div}
	def __init__(self, fn):
		# Parse file
		lines = [l.split() for l in file(fn, 'rb').read().strip().split('\n')]
		if (lines[0][0] != '#'):
			raise Exception('Puzzle definitions must begin with a size ("#") line')
		self.size	= int(lines[0][1])
		self.cages	= [self.lut[l[0]](*l[1:]) for l in lines[1:]]

As you can see, this class initializes itself from a file. I did this because I wanted to be able to write things like:

>>> p = Puzzle('9x9.txt')

or

>>> solve(Puzzle('9x9.txt'))

Unfortunately, this was the wrong way to do it. The Puzzle class’ constructor became quite a problem; it prevented me from building Puzzles on the fly, creating copy functions, and so forth.

The Puzzle constructor was particularly egregious because it involved very cumbersome operations (file system manipulation). In a way, this was lucky: If it hadn’t been so badly designed, I might have been able to work around its problems, and not realized my mistake.

The Fix

Here’s a better way to organize the class:

class Puzzle(object):
	lut = {'!':Assert, '+':Sum, '-':Diff, '*':Prod, '/':Div, 's':Set}
	def __init__(self, size=0, cages=[]):
		self.size	= size
		self.cages	= cages
	def load(self, fn):
		# Parse file
		lines = [l.split() for l in file(fn, 'rb').read().strip().split('\n')]
		if (lines[0][0] != '#'):
			raise Exception('Puzzle definitions must begin with a size ("#") line')
		self.size	= int(lines[0][1])
		self.cages	= [self.lut[l[0]](*l[1:]) for l in lines[1:]]
		# Helpful
		return self

The class now has a simple constructor, and a load() method which handles files. Note that the load() method returns its Puzzle object, which lets us write things like:

>>> solve(Puzzle().load('9x9.txt'))

This formulation is only a little wordier (7 characters) than the form available with the specialized constructor, and the class as a whole is now much more flexible.

Conclusion

In general, if you’re only allowed one constructor per class, keep that constructor simple. In this case, I think I was mislead by my C++ background, which acclimated me to an environment of zillions of specialied constructors.

Share and Enjoy:
  • Twitter
  • Facebook
  • Digg
  • Reddit
  • HackerNews
  • del.icio.us
  • Google Bookmarks
  • Slashdot
This entry was posted in Python, The Hard Way. Bookmark the permalink.

Comments are closed.