Conflicting needs for __init__ method
Steven D'Aprano
steve at REMOVEME.cybersource.com.au
Sun Jan 14 22:43:55 EST 2007
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
More information about the Python-list
mailing list