views:

5698

answers:

6

I can't find a definitive answer for this. AFAIK, you can't have multiple __init__ functions in a Python class. So what is a good way to solve this problem?

Suppose I have an class called Cheese with the number_of_holes property. How can I have two ways of creating cheese-objects...

  • one that takes a number of holes like this: parmesan = Cheese(num_holes = 15)
  • and one that takes no arguments and just randomizes the number_of_holes property: gouda = Cheese()

I can think of only one way to do this, but that seems kinda clunky:

class Cheese():
    def __init__(self, num_holes = 0):
        if (num_holes == 0):
            # randomize number_of_holes
        else:
            number_of_holes = num_holes

What do you say? Is there a better way?

+59  A: 

Actually None is much better for "magic" values:

class Cheese():
    def __init__(self, num_holes = None):
    if(num_holes is None):
     ...

No if you want complete freedom of adding more parameters

class Cheese():
    def __init__(self, *args, **kwargs):
    #args -- tuple of anonymous arguments
    #kwargs -- dictionary of named arguments
    self.num_holes = kwargs.get('num_holes',random_holes())

to explain better concept of *args and **kwargs (you can actually change these names):

def f(*args, **kwargs):
   print 'args: ', args, ' kwargs: ', kwargs

>>> f('a')
args:  ('a',)  kwargs:  {}
>>> f(ar='a')
args:  ()  kwargs:  {'ar': 'a'}
>>> f(1,2,param=3)
args:  (1, 2)  kwargs:  {'param': 3}

http://docs.python.org/reference/expressions.html#calls

vartec
+1: The standard approach -- None for automagic defaults.
S.Lott
+1 for talking about magic :)
Perpetualcoder
+10 for brilliantly simple explanation of *args and **kwargs
hasen j
+6  A: 

Use num_holes=None as a default, instead. Then check for whether num_holes is None, and if so, randomize. That's what I generally see, anyway.

More radically different construction methods may warrant a classmethod that returns an instance of cls.

Devin Jeanpierre
+43  A: 

Using num_holes=None as the default is fine, as others have pointed out.

If you want multiple, independent "constructors", you can provide these as class methods. These are usually called factory methods.

class Cheese (object):
    def __init__(self):
        # create the basic object

    @classmethod
    def random(cls):
        obj = cls()
        obj.set_holes(random())

        return obj


    @classmethod
    def fixed(cls, num):
        obj = cls()
        obj.set_holes(num)

        return obj

Now create object like this:

gouda = Cheese.fixed(0)
emmentaler = Cheese.random()
Ber
This is the cleaner way in my opinion. Simply beautiful.
Manuel Ceron
I think this is cleaner as well. Here's a link to an even clearer explication, IMHO -- from a comp.lang.python post, "Re: Multiple constructors" by Alex Martelli. http://coding.derkeiler.com/Archive/Python/comp.lang.python/2005-02/1294.html
ariddell
+8  A: 

Why do you think your solution is "clunky"? Personally I would prefer one constructor with default values over multiple overloaded constructors in situations like yours (Python does not support method overloading anyway):

def __init__(self, num_holes=None):
    if num_holes is None:
        # Construct a gouda
    else:
        # custom cheese
    # common initialization

For really complex cases with lots of different constructors, it might be cleaner to use different factory functions instead:

@classmethod
def create_gouda(cls):
    c = Cheese()
    # ...
    return c

@classmethod
def create_cheddar(cls):
    # ...

In your cheese example you might want to use a Gouda subclass of Cheese though...

Ferdinand Beyer
I agree... Gouda is important :)
winsmith
+5  A: 

All of these answers are excellent if you want to use optional parameters, but another Pythonic possibility is to use a classmethod to generate a factory-style pseudo-constructor:

def __init__(self, num_holes):

  # do stuff with the number

@classmethod
def fromRandom(cls):

  return cls( # some-random-number )
Jekke
+3  A: 

The best answer is the one above about default arguments, but I had fun writing this, and it certainly does fit the bill for "multiple constructors". Use at your own risk.

What about the new method.

"Typical implementations create a new instance of the class by invoking the superclass’s new() method using super(currentclass, cls).new(cls[, ...]) with appropriate arguments and then modifying the newly-created instance as necessary before returning it."

So you can have the new method modify your class definition by attaching the appropriate constructor method.

class Cheese(object):
    def __new__(cls, *args, **kwargs):

        obj = super(Cheese, cls).__new__(cls)
        num_holes = kwargs.get('num_holes', random_holes())

        if num_holes == 0:
            cls.__init__ = cls.foomethod
        else:
            cls.__init__ = cls.barmethod

        return obj

    def foomethod(self, *args, **kwargs):
        print "foomethod called as __init__ for Cheese"

    def barmethod(self, *args, **kwargs):
        print "barmethod called as __init__ for Cheese"

if __name__ == "__main__":
    parm = Cheese(num_holes=5)
mluebke
whoa... that seems like strong kung fu!
winsmith
This is the sort of code that gives me nightmares about working in dynamic languages--not to say that there's anything inherently wrong with it, only that it violates some key assumptions I would make about a class.
Jekke