My Take on Multiple Constructors
I noticed the same question on c.l.p that Steve Ferg responded to on his blog. I was feeling too lazy to respond to the thread earlier but I thought I’d throw my idea up on the ol’ blog before wrapping up for the day.
I think this is a classic use-case for class methods. Here is my implementation.
class Vector(object): def __init__(self, x, y, z): self.x = x self.y = y self.z = z @classmethod def from_sequence(cls, sequence): return cls(*sequence) @classmethod def from_vector(cls, vec): return cls(vec.x, vec.y, vec.z) def __repr__(self): return "Vector(%s, %s, %s)" % (self.x, self.y, self.z) if __name__ == '__main__': print Vector(1,2,3) print Vector.from_sequence([4,5,6]) print Vector.from_sequence((7,8,9)) v = Vector(10, 11, 12) print Vector.from_vector(v);
Here is the output from running the script:
Vector(1, 2, 3) Vector(4, 5, 6) Vector(7, 8, 9) Vector(10, 11, 12)
I like the classmethod route because it is obvious what the code is doing, it makes it easy to add new from_* methods and keeps the general __init__ method clean.
cw
March 17th, 2010 at 5:51 pm
FWIW I think a_vector.copy() would be more Pythonic than Vector.from_vector(a_vector).
March 17th, 2010 at 6:21 pm
100% agree. I just wanted to highlight another form of a constructor like Steve did in his post.
March 17th, 2010 at 8:14 pm
I prefer your multiple constructors approach to Steve’s isinstance checks as well. isinstance is fragile and harms readability IMO.
The classmethod approach you show can also be dangerous, though. Alternate constructors written as classmethods necessarily know the structure of the thing being passed in, so they violate “tell, don’t ask” almost by definition. (Maybe literally by definition; I haven’t though enough about it.) I suspect that such alternate constructors are better written as conversion methods on the source class, which is what Marius suggested. I view it as a fundamental OO design issue, though, not just an issue of pythonicity.
It’s better for me to worry about the pieces of myself, and give them to someone else who knows how to assemble them into something else. I am my responsibility; other people are other peoples’.
With that said, I do write a fair amount of classmethods-as-alternate-constructors. I suspect that it’s wrong, but I lack the subtle design knowledge to know or claim that it’s definitely wrong.
March 18th, 2010 at 1:39 am
In this particular case I might be inclined to use the “*” operator (ie. Vector(*[2, 4, 5])) to get past the list issue. If the coder doesn’t like *, he can implement a factory or something to work around it.
I just peeked at my own implementation of Vector (did it like a year or two ago
) and noticed that I happened to use a definition along __init__(self, *axes). The nice thing about this is that it’s generic yet retains the essential semantics of a Vector. I implemented common axes x, y, z and w via property access.
I agree that the (self, *args, **kwargs) solution is a bit fugly. It’s cooler to provide a simple interface than something really ambiguous like that.
March 19th, 2010 at 5:10 am
You should give a try to http://pypi.python.org/pypi/PEAK-Rules
March 22nd, 2010 at 1:17 pm
That’s the approach I tend to favour. I like that it is explicit.
You may want to avoid calling the constructor at all, as there may be code in __init__ that isn’t required in the ‘alternative constructor’.
Here’s how I did it in my Vector class:
http://code.google.com/p/gameobjects/source/browse/trunk/gameobjects/vector3.py