9

I have a list of dictionaries like this:

[{"foo" : "bar", "myKey" : "one"}, 
{"foo" : "bar", "myKey" : "two"}, 
{"foo" : "bar", "yourKey" : "three"}]

I'd like to sort it by a key in the dictionary if it exists.

featured = sorted(filesToWrite, key=lambda k: k["myKey"])

This doesn't work if "myKey" doesn't exist. EDIT: If myKey doesn't exist in the dictionary, I'd like it to appear at the end of the list.

I could loop through the list manually and do it myself but I'm sure there is a pythonic way to accomplish my goal without doing all that.

Brad
  • 6,106
  • 4
  • 31
  • 43

4 Answers4

19

Check out dict.get:

featured = sorted(filesToWrite, key=lambda k: ("myKey" not in k, k.get("myKey", None)))

Output:

[{'foo': 'bar', 'myKey': 'one'}, {'foo': 'bar', 'myKey': 'two'}, {'yourKey': 'three', 'foo': 'bar'}]

The magic happens in the key:

("myKey" in k, k.get("myKey", None)

Which is a two item tuple, like:

(True, "one")

Where the first element is True/False depending on whether or not the key is missing (True comes after False hence the not), and the second element is the value of said key, if it exists. If not, None. (that argument can be skipped, but I included it to be explicit)

mhlester
  • 22,781
  • 10
  • 52
  • 75
  • confused. I don't want yourKey at all...I just want anything without "myKey" should be at the end – Brad Jan 30 '14 at 01:39
1

If you're really stuck (like I was, due to the structure of the data I was attempting to sort), you may want to break out of the lambda design, at least for diagnostic purposes. Only when I applied the "nonesorter" design shown here did I receive a meaningful diagnostic from Python that showed me that the thing I was sorting wasn't what I thought it was.

More generally: the "lambda"s you see above are shorthand that can be replaced by a full method definition that you can step through line by line with a debugger.

Also, somewhat misleading to the neophyte is the label key= in Python's sort syntax. This is not actually the name of a key field in the dictionary but rather a pointer to a function which will determine order, analogous to Perl's sort parameter called SUBNAME.

As Python.org's Sorting HOW TO describes it,

The value of the key parameter should be a function that takes a single argument and returns a key to use for sorting purposes. This technique is fast because the key function is called exactly once for each input record.

Finally, note that as others have stated, None was a legal item for comparison in Python 2 whereas with Python 3 attempting to compare to None generates a compiler exception.

Community
  • 1
  • 1
CODE-REaD
  • 2,819
  • 3
  • 33
  • 60
0
>>> L = [{"foo" : "bar", "myKey" : "one"}, 
... {"foo" : "bar", "myKey" : "two"}, 
... {"foo" : "bar", "yourKey" : "three"}]
>>> 
>>> sorted(L, key=lambda d:("myKey" not in d, d.get("myKey")))
[{'foo': 'bar', 'myKey': 'one'}, {'foo': 'bar', 'myKey': 'two'}, {'yourKey': 'three', 'foo': 'bar'}]
John La Rooy
  • 295,403
  • 53
  • 369
  • 502
  • No. This doesn't work for UTF-8 or non-string values. This unnecessarily relies on some behavior of sorted(). – IceArdor Jan 30 '14 at 02:06
  • @IceArdor, Yes, I deleted the second way. Not sure what you mean about relying on the behaviour of `sorted`. It is documented behaviour afterall. – John La Rooy Jan 30 '14 at 02:08
  • using `sorted(L, key=lambda d:d.get("myKey","\xff"))` relies on how sorted compares an ASCII string to a non-ASCII string or non-string. While my version of Jython happens to sort None and numbers before strings, that could change in future. I wouldn't personally want to chase down a bug to discover that the programmer relied on `sorted`'s interpretation of data type precedence. – IceArdor Jan 30 '14 at 02:16
0

What about

sorted((e for e in case if 'myKey' in e), key=lambda x: x['myKey']) + [e for e in case if 'myKey' not in e]

...?

Assuming the 'case' variable holds the list of dicts you just mentioned. I also assume that if the key is not present, it should be ignored, which seems to make sense.

Mariano Anaya
  • 1,246
  • 10
  • 11