Conflicting needs for __init__ method

Steven D'Aprano steve at REMOVEME.cybersource.com.au
Tue Jan 16 04:02:05 CET 2007


On Tue, 16 Jan 2007 08:54:09 +1100, Ben Finney wrote:

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

[snip]

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

[snip]

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

That's one way of looking at it. Another way is to consider that __init__
has one function: it turns something else into a Rational. Why should the
public interface of "make a Rational" depend on what you are making it
from?

Think of built-ins like str() and int(). I suggest that people would be
*really* unhappy if we needed to do this:

str.from_int(45)
str.from_float(45.0)
str.from_list([45, 45.5])
etc.

Why do you consider that Rationals are different from built-ins in this
regard?


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

But that could just as easily be written as:

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

which then raises the question, why delegate the addition out of __add__
to perform_addition? There is at least three distinct costs: a larger
namespace, an extra function to write tests for; and an extra method
call for every addition. What benefit do you gain? Why not put the
perform_addition code directly in __add__?

Just creating an extra layer to contain the complexity of rational
addition doesn't gain you anything -- you haven't done anything to reduce
the complexity of the problem, but you have an extra layer to deal with.

And you still haven't dealt with another problem: coercions from other
types. If you want to be able to add Rationals to (say) floats, ints and
Rationals without having to explicitly convert them then you need some
method of dispatching to different initialiser methods. (You should be
asking whether you really do need this, but let's assume you do.)

Presumably you create a method Rational.dispatch_to_initialisers that
takes any object and tries each initialiser in turn until one succeeds,
then returns the resultant Rational. Or you could just call it
Rational.__init__.

This doesn't mean that __init__ must or even should contain all the
initialisation logic -- it could dispatch to from_string, from_float and
other methods. But the caller doesn't need to call the individual
initialisers -- although of course they are public methods and can be
called if you want -- since __init__ will do the right thing.


-- 
Steven D'Aprano 




More information about the Python-list mailing list