25

When using dictionary (dict) keys in Python, there seem to be a few general approaches:

  1. some_dict['key_name'] # string constants everywhere

  2. some_dict[KeyConstants.key_name] # where class KeyConstants: key_name: 'key_name'

  3. some_dict[KEY_NAME] # with from some_module import KEY_NAME # a module level constant

    1. 'key_name' has the disadvantage that you're repeating constants throughout your code. It's not DRY. Worse, if you ever go to publish an your API (in the broadest sense) you'll have consumers of your API repeating these constants everywhere, and if you ever want to change 'key_name' to 'better_key_name' it will be a breaking change.

    2. This is the typed language, DRY approach, with constants consolidated in one place. Its only disadvantages are that it's ugly, a bit less readable, and more verbose. Pythonic principles primarily prohibit that. It lets you easily change the constant representing the key, since everyone's coding against the variable KeyConstants.key_name. It also works well with IDEs for refactoring.

    3. Module level constants are recommended in the PEP 8 style guide. ALL_CAPS_ARE_LOUD and harder to type. This has some of the advantages of both options 1 and 2.

What are some other best practices for dict key constants? Which of the above approaches are preferred and when?

Tsagana Nokhaeva
  • 630
  • 1
  • 6
  • 25
velshin
  • 291
  • 1
  • 3
  • 6
  • 3
    Why have so many constant keys in the first place? Why not use module-level variables or a record-like object holding attributes? –  May 22 '11 at 14:42
  • 6
    dicts are good if the keys are variable, else you can go with named tuples or attributes on a class. Using constant keys should be a very rare occasion. – Jochen Ritzel May 22 '11 at 15:06
  • 3
    The dict with keys issue is extremely common when using JSON or dealing with a database/nosql store without an ORM (e.g. pymongo without MongoEngine). While you can wrap dict keys into classes with attributes, you'll end up with a bunch of ugly packing/unpacking plumbing code if the underlying data was dict to begin with. – velshin May 22 '11 at 19:36

2 Answers2

9
  1. d['key_name']
  2. d[Keys.key_name]
  3. d[KEY_NAME]

I don't really view #3 as requiring module-level imports; they just can be in the module namepsace, e.g. you could do something like How to programmatically set a global (module) variable?

The advantage of #2 over #1 is that typos and obsolete values will throw an attribute error "this key doesn't exist!" rather than an index error "could not be found!" -- which is always better. #2>#1. It's not more verbose either, because you just set K=Keys (or something) if you're typing a lot, so you have d[K.key_name], just two characters more (). For example, depending how before I'm feeling, I may do either:

import subprocess as proc
proc.Popen(..., stdout=proc.PIPE)

or

import subprocess as proc
PIPE = proc.PIPE
proc.Popen(..., stdout=PIPE)

or

from subprocess import *
Popen(..., stdout=PIPE)

With regards to #3, ALL_CAPS_ARE_LOUD for a reason; it becomes confusing to distinguish between d[someVariable] (which can be holding any keyword) and d[magicKeyword] -- whereas d[MAGIC_KEYWORD] is unambiguous that it is a constant, and not some variable which may be holding a constant, e.g. for someVariable in magicKeywords. #3 basically is equivalent to #2, e.g. re.DOTALL (the re being equivalent to KeyConstants, without having to remember the name of the KeyConstants containers because it is the module). Thus #3 is superior to #2 unless you are in a strange situation where you have different types of keyspaces.

DRY / OAOO is very very important, but ultimately is not relevant to any of these, because you always need to repeat a variable name in order to refer to it; the best you can do is create an alias.

You could also consider #4, which is to endow your dictionary with attributes, e.g. d.key_name -- this is only appropriate if it's some subscriptable object.

But to quote a comment by Jochen Ritzel: "Using constant keys should be a very rare occasion" (use attributes of an object, or as he suggests perhaps a named tuple, though I've always found them to be unwieldy)

Community
  • 1
  • 1
ninjagecko
  • 88,546
  • 24
  • 137
  • 145
  • 1
    Thanks for the thoughtful reply. Note that with any of the NAMED_CONSTANTS we're DRYing the value of the constant; this applies even though they're keys. When your keys are mapped out to json or a database, it becomes clearer why the dict key name and the NAMED_CONSTANT_KEY could vary independently. – velshin May 22 '11 at 16:54
1

It is an old question, but have you checked out Bunch? It is a dictionary that supports attribute-style access, a la JavaScript.

>>> from bunch import bunchify
>>> from bunch import unbunchify
>>> import datetime as dt

>>> my_dict = {'a': 'a', 'b': [{'c': 'c', 'n': 1}, {'c': 'k', 'n': 2}], 'dt': dt.datetime.utcnow()}

>>> my_dict_obj = bunchify(my_dict) 
>>> my_dict_obj.b[0].c
'c'
>>>  'a' in my_dict_obj
True
>>>  'x' in my_dict_obj
False
>>> my_dict = unbunchify(my_dict_obj)
Shirish Kumar
  • 1,532
  • 17
  • 23