On Sun, 14 Jan 2007 15:32:35 -0800, dickinsm wrote: > Suppose you're writing a class "Rational" for rational numbers. The > __init__ function of such a class has two quite different roles to > play. First, it's supposed to allow users of the class to create > Rational instances; in this role, __init__ is quite a complex beast. > It needs to allow arguments of various types---a pair of integers, a > single integer, another Rational instance, and perhaps floats, Decimal > instances, and suitably formatted strings. It has to validate the > input and/or make sure that suitable exceptions are raised on invalid > input. And when initializing from a pair of integers---a numerator > and denominator---it makes sense to normalize: divide both the > numerator and denominator by their greatest common divisor and make > sure that the denominator is positive. > > But __init__ also plays another role: it's going to be used by the > other Rational arithmetic methods, like __add__ and __mul__, to return > new Rational instances. For this use, there's essentially no need for > any of the above complications: it's easy and natural to arrange that > the input to __init__ is always a valid, normalized pair of integers. > (You could include the normalization in __init__, but that's wasteful
Is it really? Have you measured it or are you guessing? Is it more or less wasteful than any other solution? > when gcd computations are relatively expensive and some operations, > like negation or raising to a positive integer power, aren't going to > require it.) So for this use __init__ can be as simple as: > > def __init__(self, numerator, denominator): > self.numerator = numerator > self.denominator = denominator > > So the question is: (how) do people reconcile these two quite > different needs in one function? I have two possible solutions, but > neither seems particularly satisfactory, and I wonder whether I'm > missing an obvious third way. The first solution is to add an > optional keyword argument "internal = False" to the __init__ routine, > and have all internal uses specify "internal = True"; then the > __init__ function can do the all the complicated stuff when internal > is False, and just the quick initialization otherwise. But this seems > rather messy. Worse than messy. I guarantee you that your class' users will, deliberately or accidentally, end up calling Rational(10,30,internal=True) and you'll spent time debugging mysterious cases of instances not being normalised when they should be. > The other solution is to ask the users of the class not to use > Rational() to instantiate, but to use some other function > (createRational(), say) instead. That's ugly! And they won't listen. > Of course, none of this really has anything to do with rational > numbers. There must be many examples of classes for which internal > calls to __init__, from other methods of the same class, require > minimal argument processing, while external calls require heavier and > possibly computationally expensive processing. What's the usual way > to solve this sort of problem? class Rational(object): def __init__(self, numerator, denominator): print "lots of heavy processing here..." # processing ints, floats, strings, special case arguments, # blah blah blah... self.numerator = numerator self.denominator = denominator def __copy__(self): cls = self.__class__ obj = cls.__new__(cls) obj.numerator = self.numerator obj.denominator = self.denominator return obj def __neg__(self): obj = self.__copy__() obj.numerator *= -1 return obj I use __copy__ rather than copy for the method name, so that the copy module will do the right thing. -- Steven D'Aprano -- http://mail.python.org/mailman/listinfo/python-list