15

I find myself needing to iterate over a list made of dictionaries and I need, for every iteration, the name of which dictionary I'm iterating on.

Here's an MRE (Minimal Reproducible Example).
Contents of the dicts are irrelevant:

dict1 = {...}
dicta = {...}
dict666 = {...}

dict_list = [dict1, dicta, dict666]

for dc in dict_list:
    # Insert command that should replace ???
    print 'The name of the dictionary is: ', ???

If I just use dc where ??? is, it will print the entire contents of the dictionary. How can I get the name of the dictionary being used?

martineau
  • 119,623
  • 25
  • 170
  • 301
Gabriel
  • 40,504
  • 73
  • 230
  • 404
  • 2
    Dictionaries do not inherently have names. Variables, as a general rule, do not inherently have names. If you're trying to get the name of the variable you assigned it to, I don't think that can be done, since you could have multiple variables pointing to the same dictionary. – TheSoundDefense Aug 05 '14 at 19:01
  • 1
    http://stackoverflow.com/questions/2553354/how-to-get-a-variable-name-as-a-string-in-python – Ashalynd Aug 05 '14 at 19:01

6 Answers6

21

Don't use a dict_list, use a dict_dict if you need their names. In reality, though, you should really NOT be doing this. Don't embed meaningful information in variable names. It's tough to get.

dict_dict = {'dict1':dict1, 'dicta':dicta, 'dict666':dict666}

for name,dict_ in dict_dict.items():
    print 'the name of the dictionary is ', name
    print 'the dictionary looks like ', dict_

Alternatively make a dict_set and iterate over locals() but this is uglier than sin.

dict_set = {dict1,dicta,dict666}

for name,value in locals().items():
    if value in dict_set:
        print 'the name of the dictionary is ', name
        print 'the dictionary looks like ', value

Again: uglier than sin, but it DOES work.

Adam Smith
  • 52,157
  • 12
  • 73
  • 112
  • _Don't embed meaningful information in variable names_, but variable names **are** meaningful information. – Gabriel Aug 05 '14 at 19:03
  • 3
    @Gabriel if the name is meaningful, it should be in a dict where you can access it. Otherwise treat it as a throwaway that's easier to remember than a memory location (it's really just a pointer to `0xFAC4928FD9` anyway) – Adam Smith Aug 05 '14 at 19:05
  • 4
    @Gabriel any information that's actually meaningful to your program should be stored in variable data, not a variable name. – TheSoundDefense Aug 05 '14 at 19:06
  • 1
    A better way of phrasing it would be "don't embed information that is meaningful _to the program_ in variable names", as it is hard to retrieve and easily broken by code changes. – Colonel Thirty Two Aug 05 '14 at 20:06
  • 1
    @Gabriel: Also see [_Keep data out of your variable names_](http://nedbatchelder.com/blog/201112/keep_data_out_of_your_variable_names.html) for some good reasons not to do it. – martineau Aug 05 '14 at 20:52
  • Thanks everybody, I'll keep this information in mind. – Gabriel Aug 05 '14 at 21:10
  • 1
    @Gabriel don't feel too admonished. Everybody did it at some point which is why it's now so strongly suggested NOT to do that. I myself wrote a script about a year ago that had to go gather some filesize information for about 12 files each off ~100 machines to find out which machines weren't purging their history files. I designed it as `FILENAME = [('machine_name', size), ...]` for each file, and it was the biggest hackiest piece of crap script I'd ever written. I can't even use it anymore because I don't know how to maintain it anymore -- had to re-write from scratch a month ago. – Adam Smith Aug 05 '14 at 23:18
  • The OP's question is legitimate for automation. For example, DNA sequencing. Let's say I have 1000 samples, and for each sample, I want to compare the sample sequence with a user-specified list of input reference sequences. Instead of building a dictionary sample by sample, I can build a dictionary once for each input reference region, then for each sample, pull out the appropriate reference dictionary by name from a list of dictionaries, and compare between reference dictionary and sample. So instead of computing 1000's of dictionaries, I compute a limited list of them, just once. – SummerEla Mar 22 '19 at 20:11
  • @SummerEla isn't that what `functools.lru_cache` is for? – Adam Smith Mar 22 '19 at 21:51
  • @AdamSmith I haven't seen it used in this context; but would love to see an example! – SummerEla Mar 23 '19 at 17:38
  • @SummerEla hmm maybe I misunderstood. I thought your concern was that you had an expensive function call that you didn't want to repeat over many many possible identical data points (which is where lru_cache shines). I guess I don't really understand your use case, and why a list of dictionaries where you have to divine the name bound to each one is superior to a dictionary of dictionaries keyed by their name? – Adam Smith Mar 23 '19 at 18:55
4

You should also consider adding a "name" key to each dictionary.

The names would be:

for dc in dict_list:
    # Insert command that should replace ???
    print 'The name of the dictionary is: ', dc['name']
Jeff Tsui
  • 1,266
  • 12
  • 20
3

Objects don't have names in Python, a name is an identifier that can be assigned to an object, and multiple names could be assigned to the same one.

However, an object-oriented way to do what you want would be to subclass the built-in dict dictionary class and add a name property to it. Instances of it would behave exactly like normal dictionaries and could be used virtually anywhere a normal one could be.

class NamedDict(dict):
    def __init__(self, *args, **kwargs):
        try:
            self._name = kwargs.pop('name')
        except KeyError:
            raise KeyError('a "name" keyword argument must be supplied')
        super(NamedDict, self).__init__(*args, **kwargs)

    @classmethod
    def fromkeys(cls, name, seq, value=None):
        return cls(dict.fromkeys(seq, value), name=name)

    @property
    def name(self):
        return self._name

dict_list = [NamedDict.fromkeys('dict1', range(1,4)),
             NamedDict.fromkeys('dicta', range(1,4), 'a'),
             NamedDict.fromkeys('dict666', range(1,4), 666)]

for dc in dict_list:
    print 'the name of the dictionary is ', dc.name
    print 'the dictionary looks like ', dc

Output:

the name of the dictionary is  dict1
the dictionary looks like  {1: None, 2: None, 3: None}
the name of the dictionary is  dicta
the dictionary looks like  {1: 'a', 2: 'a', 3: 'a'}
the name of the dictionary is  dict666
the dictionary looks like  {1: 666, 2: 666, 3: 666}
martineau
  • 119,623
  • 25
  • 170
  • 301
2

If you want to read name and value

dictionary={"name1":"value1","name2":"value2","name3":"value3","name4":"value4"}
for name,value in dictionary.items():
    print(name)
    print(value)

If you want to read name only

dictionary={"name1":"value1","name2":"value2","name3":"value3","name4":"value4"}
for name in dictionary:
    print(name)

If you want to read value only

dictionary={"name1":"value1","name2":"value2","name3":"value3","name4":"value4"}
for values in dictionary.values():
    print(values)

Here is your answer

dic1 = {"dic":1}
dic2 = {"dic":2}
dic3 = {"dic":3}
dictionaries = [dic1,dic2,dic3]
for i in range(len(dictionaries)):
  my_var_name = [ k for k,v in locals().items() if v == dictionaries[i]][0]
  print(my_var_name)
Habibur Rahman
  • 194
  • 2
  • 8
0

The following doesn't work on standard dictionaries, but does work just fine with collections dictionaries and counters:

from collections import Counter

# instantiate Counter ditionary
test= Counter()

# create an empty name attribute field
test.name = lambda: None

# set the "name" attribute field to "name" = "test"
setattr(test.name, 'name', 'test')

# access the nested name field
print(test.name.name)

It's not the prettiest solution, but it is easy to implement and access.

SummerEla
  • 1,902
  • 3
  • 26
  • 43
0

Here's my solution for a descriptive error message.

def dict_key_needed(dictionary,key,msg='dictionary'):
    try:
        value = dictionary[key]
        return value
    except KeyError:
         raise KeyError(f"{msg} is missing key '{key}'")
Carl Brubaker
  • 1,602
  • 11
  • 26