Looking for feedback on my program design
<p>I'm aware that SO is for questions but overall the aim is to help people learn so I figured I'd try my hand at sharing some code and asking for feedback on it.</p>

<p><hr></p>

<p>I'm looking to create a program that will rely on random numbers, specifically dice. These will be presented in the form of "2D6", "4D10+3", "2D2 + 3D3" and so on and so forth. I thus set out to create a dice roller module that would be able to accept input like in that form.</p>

<p>It works just fine for what's needed but has a bug for things that probably won't be needed (the docstring at the start of the file should explain). What I am interested in is what people think of my code and if anybody can see ways to improve it.</p>

<p>It is still WIP and I've not started on the unit tests yet.</p>

<p><a href="http://woarl.com/dice.txt" rel="nofollow">Link to code</a></p>

<pre><code>#!/usr/bin/env python3
"""
Created by Teifion Jordan
http://woarl.com

Notes: The roller does not correctly apply * and / signs:
A + B * C is worked out as (A + B) * C, not A + (B * C) as would be correct
"""

import random
import re
import math

class Roller_dict (object):
    """A 'dictionary' that stores rollers, if it's not got that roller it'll make a new one"""
    def __init__(self, generator=random.randint):
     super(Roller_dict, self).__init__()
     self.rollers = {}

     # Generator is used to supply a "rigged" random function for testing purposes
     self.generator = generator

    def __call__(self, constructor):
     constructor = constructor.replace(" ", "")
     if constructor not in self.rollers:
      self.rollers[constructor] = Roller(constructor, self.generator)

     return self.rollers[constructor]()

# Regular expressions used by the Roller class
# Compiled here to save time if we need to make lots of Roller objects
pattern_split    = re.compile(r"(\+|-|\*|/)")
pattern_constant    = re.compile(r"([0-9]*)")
pattern_die   = re.compile(r"([0-9]*)[Dd]([0-9]*)")
pattern_sign     = re.compile(r"^(\+|-|\*|/)")

class Roller (object):
    def __call__(self):
     return self.roll()

    def __init__(self, constructor, generator=random.randint):
     super(Roller, self).__init__()
     self.items = []
     self.rebuild(constructor)
     self.generator = generator

    def rebuild(self, constructor):
     """Builds the Roller from a new constructor string"""
     # First we need to split it up
     c = pattern_split.split(constructor.replace(" ", ""))

     # Check for exceptions
     if len(c) == 0:
      raise Exception('String "%s" did not produce any splits' % constructor)

     # Stitch signs back into their sections
     parts = []
     last_p = ""
     for p in c:
      if p in "+-*/":
       last_p = p
       continue

      if last_p != "":
       p = "%s%s" % (last_p, p)
       last_p = ""

      parts.append(p)

     # We have the parts, now we need to evaluate them into items
     for p in parts:
      # Look for a sign, default to positive
      sign = pattern_sign.search(p)
      if sign == None: sign = "+"
      else: sign = sign.groups()[0]

      # Strip out the sign, we're left with just the pure value
      body = p.replace(sign, "")

      # Now we find out what our main body is

      # Die
      value = pattern_die.search(body)
      if value != None:
       # Sign, Number, Sides
       self.items.append(("die", sign, int(value.groups()[0]), int(value.groups()[1])))
       continue

      # Constant
      value = pattern_constant.search(body)
      if value != None:
       self.items.append(("constant", sign, int(value.groups()[0])))
       continue

      # No matches
      raise Exception('The part string "%s" had no matches' % body)

    def roll(self):
     """Rolls the die/dice and returns the result"""
     result = 0

     for i in self.items:
      # Get value
      if i[0] == "die":   value = self._derive_die(i[2], i[3])
      elif i[0] == "constant": value = self._derive_constant(i[2])
      else: raise Exception('No handler for item type "%s"' % i[0])

      # Apply sign
      if i[1] == "+":  result += value
      elif i[1] == "-": result -= value
      elif i[1] == "*": result *= value
      elif i[1] == "/": result /= value

     return result

    def _derive_die(self, number, sides):
     result = 0
     for n in range(0, number):
      result += self.generator(0, sides)

     return result

    def _derive_constant(self, value):
     return value

# Useful for running the tests to make sure that it uses "random" numbers
false_numbers = (int(math.cos(x)*5)+5 for x in range(0,1000))
def false_numbers_func(*args):
    return false_numbers.next()

# If it's main, run unit tests?
if __name__ == '__main__':
    r = Roller_dict(false_numbers_func)

    print(r("2D6"))
    print(r("2D6"))
    print(r("2D6"))
</code></pre>
