Conflicting needs for __init__ method

Ben Finney bignose+hates-spam at benfinney.id.au
Mon Jan 15 16:54:09 EST 2007


dickinsm at gmail.com writes:

> Suppose you're writing a class "Rational" for rational numbers.  The
> __init__ function of such a class has two quite different roles to
> play.

That should be your first clue to question whether you're actually
needing separate functions, rather than trying to force one function
to do many different things.

> First, it's supposed to allow users of the class to create Rational
> instances; in this role, __init__ is quite a complex beast.

The __init__ function isn't the "constructor" you find in other
languages. Its only purpose is to initialise an already-created
instance, not make a new one.

> 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.

All of this points to having a separate constructor function for each
of the inputs you want to handle.

> 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.

No, it won't; those methods won't "use" the __init__ method. They will
use a constructor, and __init__ is not a constructor (though it does
get *called by* the construction process).

> 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.

Therefore, make your __init__ handle just the default, natural case
you identify.

    class Rational(object):
        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?

By avoiding the tendency to crowd a single function with disparate
functionality. Every function should do one narrowly-defined task and
no more.

        @classmethod
        def from_string(input):
            (n, d) = parse_elements_of_string_input(input)
            return Rational(n, d)

        @classmethod
        def from_int(input):
            return Rational(input, 1)

        @classmethod
        def from_rational(input):
            (n, d) = (input.numerator, input.denominator)
            return Rational(n, d)

        def __add__(self, other):
            result = perform_addition(self, other)
            return result

        def __sub__(self, other):
            result = perform_subtraction(self, other)
            return result

Put whatever you need to for 'parse_elements_of_string_input',
'perform_addition', 'perform_subtraction', etc; either the calculation
itself, if simple, or a call to a function that can contain the
complexity.

Use Python's exception system to avoid error-checking all over the
place; if there's a problem with the subtraction, for instance, let
the exception propagate up to the code that gave bad input.

The alternate constructors are decorated as '@classmethod' since they
won't be called as instance methods, but rather:

    foo = Rational.from_string("355/113")
    bar = Rational.from_int(17)
    baz = Rational.from_rational(foo)

-- 
 \          "If you can't beat them, arrange to have them beaten."  -- |
  `\                                                     George Carlin |
_o__)                                                                  |
Ben Finney




More information about the Python-list mailing list