1

Here is what I have so far:

class Die (object):
    def __init__(self,sides):
        self.sides = sides

    def roll(self):
        return random.randint(1,self.sides)

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

    def __unicode__(self):
        return "1d%d" % (self.sides)

    def __str__(self):
        return unicode(self).encode('utf-8')

class Dice (object):
    def __init__(self, num_dice, sides):
        self.die_list = [Die(sides)]*num_dice

    def __init__(self, *dice):
        self.die_list = dice

    def roll(self):
        return reduce(lambda x, y: x.roll() + y.roll(), self.die_list)

But when I try to do Dice(3,6) and subsequently call the roll action it says it can't because 'int' object has no attribute 'roll'. That means it's going into the varargs constructor first. What can do I do here to make this work, or is there another alternative?

SilentGhost
  • 307,395
  • 66
  • 306
  • 293
Hiro2k
  • 5,254
  • 4
  • 23
  • 28
  • it doesn't "go into varargs constructor first". In the class definition all methods are unique, there aren't two constructor (they're actually initialisers) there. Your `die_list` is a tuple of integers that pass. – SilentGhost Jan 29 '11 at 20:42

2 Answers2

4

As you observed in your question, the varargs constructor is being invoked. This is because the second definition of Dice.__init__ is overriding, not overloading, the first.

Python doesn't support method overloading, so you have at least two choices at hand.

  • Define only the varargs constructor. Inspect the length of the argument list and the types of the first few elements to determine what logic to run. Effectively, you would merge the two constructors into one.
  • Convert one of the constructors into a static factory method. For example, you could delete the first constructor, keep the varargs one, and then define a new factory method.

I prefer the second method, which allows you to cleanly separate your logic. You can also choose a more descriptive name for your factory method; from_n_sided_dice is more informative than just Dice:

@staticmethod
def from_n_sided_dice(num_dice, sides):
    return Dice([Die(sides)] * num_dice)

Side note: Is this really what you want? [Die(sides)] * num_dice returns a list with multiple references to the same Die object. Rather, you might want [Die(sides) for _ in range(num_dice)].

EDIT: You can emulate method overloading (via dynamic dispatch, not static dispatch as you may be used to, but static types do not exist in Python) with function decorators. You may have to engineer your own solution to support *args and **kwargs, and having separate methods with more precise names is still often a better solution.

Community
  • 1
  • 1
ide
  • 19,942
  • 5
  • 64
  • 106
  • All `Die` objects in he OP's code are stateless, so N copies of the same `Die` should produce the same results as N distinct `Die` objects. – Chris Lutz Jan 29 '11 at 21:02
  • For now, sure. It seems like a potential pitfall and I would have used a singleton-like API to express my intent if it was OK to reference the same Die; e.g., `[Die.with_sides(sides)] * num_dice`. – ide Jan 29 '11 at 21:07
1

What you want to have is a single __init__ method, that is defined along these lines:

class Dice (object):
    def __init__(self, *args):
        if not isinstance(args[0], Die):
            self.die_list = [Die(args[0]) for _ in range(args[1])]
        else:
            self.die_list = args
    def roll(self):
        return sum(x.roll() for x in self.die_list)
SilentGhost
  • 307,395
  • 66
  • 306
  • 293
  • Yohr current constructor makes `self.die_list` a tuple sometimes and a list other times. I would try to make it the same every time. – Chris Lutz Jan 29 '11 at 21:12