"Multi-key" dictionary I was making a relatively large-scale project in Python as a learning experiment, and in the process I found that it would be useful to have a map (or, in Python, I guess they're called dictionaries) where a value could have multiple keys.  I couldn't find much support for this out there, so this is the implementation I ended up going with:

def range_dict(*args):
    return_val = {}
    for k,v in args:
        for i in k:
            return_val[i] = v
    return return_val

COINS = range_dict((range(1,15), None),
                     (range(15,30), "1d6x1000 cp"),
                     (range(30,53), "1d8x100 sp"),
                     (range(53,96), "2d8x10 gp"),
                     (range(96,101), "1d4x10 pp"))


I was told that this wasn't really a good way to achieve what I wanted to do, but I don't really understand why.  It works perfectly in my application.  What's wrong with this?  Is there a better, more Pythonic way to do this?
1. Comments on your code


No docstrings! What does your code do and how am I supposed to use it?
No test cases. This ought to be a good opportunity to use doctests.
Given that your purpose here is to implement RPG lookup tables, your design seems a bit hard to use. The printed RPG table probably says something like this:

1-8     giant spider
9-12    skeleton
13-18   brass dragon
...


but you have to input it like this (adding one to the limit of each range):

range_dict((range(1, 9), "giant spider"),
           (range(9, 13), "skeleton"),
           (range(13, 18), "brass dragon"),
           ...)


which would be easy to get wrong.
The design also seems fragile, because it doesn't ensure that the ranges are contiguous. If you made a data entry mistake:

d = range_dict((range(1, 8), "giant spider"),
               (range(9, 13), "skeleton"),
               (range(13, 18), "brass dragon"),
               ...)


then the missing key would later result in an error:

&gt;&gt;&gt; d[8]
Traceback (most recent call last):
  File "&lt;stdin&gt;", line 1, in &lt;module&gt;
KeyError: 8



2. An alternative approach

Here's how I'd implement this lookup table:

from bisect import bisect_left
from collections.abc import Mapping

class LookupTable(Mapping):
    """A lookup table with contiguous ranges of small positive integers as
    keys. Initialize a table by passing pairs (max, value) as
    arguments. The first range starts at 1, and second and subsequent
    ranges start at the end of the previous range.

    &gt;&gt;&gt; t = LookupTable((10, '1-10'), (35, '11-35'), (100, '36-100'))
    &gt;&gt;&gt; t[10], t[11], t[100]
    ('1-10', '11-35', '36-100')
    &gt;&gt;&gt; t[0]
    Traceback (most recent call last):
      ...
    KeyError: 0
    &gt;&gt;&gt; next(iter(t.items()))
    (1, '1-10')

    """
    def __init__(self, *table):
        self.table = sorted(table)
        self.max = self.table[-1][0]

    def __getitem__(self, key):
        key = int(key)
        if not 1 &lt;= key &lt;= self.max:
            raise KeyError(key)
        return self.table[bisect_left(self.table, (key,))][1]

    def __iter__(self):
        return iter(range(1, self.max + 1))

    def __len__(self):
        return self.max


Notes on this implementation:


The constructor takes the maximum for each range (not the maximum plus one), making data entry less error-prone.
The minimum of each range is taken from the end of the previous range, thus ensuring that there are no gaps between ranges.
The collections.abc.Mapping abstract base class provides much of the functionality of a read-only dictionary. The idea is that you supply the __getitem__, __iter__ and __len__ methods, and the Mapping class supplies implementations of __contains__, keys, items, values, get, __eq__, and __ne__ methods for you. (Which you can override if you need to, but that's not necessary here.)
I've used bisect.bisect_left to efficiently look up keys in the sorted table.