Re: Conflicting needs for __init__ method
- From: Steven D'Aprano <steve@xxxxxxxxxxxxxxxxxxxxxxxxxxx>
- Date: Mon, 15 Jan 2007 14:43:55 +1100
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
.
- Follow-Ups:
- Re: Conflicting needs for __init__ method
- From: Mark Dickinson
- Re: Conflicting needs for __init__ method
- From: Steven D'Aprano
- Re: Conflicting needs for __init__ method
- References:
- Conflicting needs for __init__ method
- From: dickinsm
- Conflicting needs for __init__ method
- Prev by Date: Re: Conflicting needs for __init__ method
- Next by Date: on which site or Usenet group should this question be posted?
- Previous by thread: Re: Conflicting needs for __init__ method
- Next by thread: Re: Conflicting needs for __init__ method
- Index(es):