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