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.