Probability calculator project, random module error

I’m getting an error when I run the test module. The traceback shows the error is coming from a method in the random module. I don’t understand what is going wrong. It looks like at some point randrange receives an empty range, but I have no idea how or why. Below I post the traceback and my experiment function.

======================================================================
ERROR: test_prob_experiment (test_module.UnitTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/denny/Sync/freeCodeCamp/scientific_computing_with_python/probability-calculator/test_module.py", line 29, in test_prob_experiment
    probability = prob_calculator.experiment(hat=hat, expected_balls={"yellow":2,"blue":3,"test":1}, num_balls_drawn=20, num_experiments=100)
  File "/home/denny/Sync/freeCodeCamp/scientific_computing_with_python/probability-calculator/prob_calculator.py", line 42, in experiment
    balls = hat_copy.draw(num_balls_drawn)
  File "/home/denny/Sync/freeCodeCamp/scientific_computing_with_python/probability-calculator/prob_calculator.py", line 23, in draw
    idx = random.randint(0, (num_balls_in_hat - 1))
  File "/usr/lib/python3.10/random.py", line 370, in randint
    return self.randrange(a, b+1)
  File "/usr/lib/python3.10/random.py", line 353, in randrange
    raise ValueError("empty range for randrange() (%d, %d, %d)" % (istart, istop, width))
ValueError: empty range for randrange() (0, 0, 0)

----------------------------------------------------------------------
Ran 3 tests in 0.008s

FAILED (errors=1)

My experiment function:

def experiment(hat: Hat, expected_balls={}, num_balls_drawn=0, num_experiments=0) -> float:
    M = 0

    # convert expected balls from a dict to a list
    expected_balls_list = []
    for ball, quantity in expected_balls.items():
        expected_balls_list += [ball] * quantity

    for i in range(num_experiments):
        hat_copy = copy.deepcopy(hat)
        balls = hat_copy.draw(num_balls_drawn)
        
        ebl_in_bl = True
        for b in expected_balls_list:
            if b in balls:
                balls.remove(b)
            else:
                ebl_in_bl = False
                break

        if ebl_in_bl:
            M += 1

    return (M / num_experiments)

How your draw method looks like? Looking just at the error message, perhaps there’s situation when only one ball is left in the hat, what would result in passing two 0s to the randint.

My draw method:

    def draw(self, num_balls: int) -> list[str]:
        balls = []
        num_balls_in_hat = len(self.contents)
        for i in range(num_balls):
            idx = random.randint(0, (num_balls_in_hat - 1))
            try:
                balls.append(self.contents.pop(idx))
                num_balls_in_hat = len(self.contents)
            except:
                continue

        return balls

I was a bit wrong, error happens when there’s 0 balls in hat. this makes call random.randint(0, -1) which results with exception.

I believe it is expected, that once all balls are drawn from the hat, they are supposed to be returned to it.