Python sort list of dicts by multiple dynamic keys, with None values at last position

Question:

I want to sort a list of dicts (dict_list) (that can also contain nested dicts) by a list of received keys (groups).
The values inside the dicts can generally be None.

After sorting, None values should generally be palced in the last position.
Example when i have the following group list and dict list

groups = ["key1", "key3.key4"]
dict_list = [
    {
        "key1": "abc",
        "key2": "def",
        "key3": {
            "key4": "ghi"
        },
        "key5": {
            "key6": "uvw"
        }
    }, 
    {
        "key1": "abc",
        "key2": "asd",
        "key3": {
            "key4": "abc"
        },
        "key5": {
            "key6": "uvw"
        }
    },    
    {
        "key1": None,
        "key2": "asd",
        "key3": {
            "key4": "abc"
        },
        "key5": {
            "key6": "uvw"
        }
    },
    {
        "key1": "abc",
        "key2": None,
        "key3": None,
        "key5": {
            "key6": "uvw"
        }
    },
    {
        "key1": "xyz",
        "key2": None,
        "key3": {
            "key4": "jklm"
        },
        "key5": {
            "key6": "uvw"
        }
    },
    {
        "key1": "abc",
        "key2": "dfd",
        "key3": {
            "key4": "ghi"
        },
        "key5": {
            "key6": "ers"
        }
    }
]

I would expect the following output:

dict_list = [
    {
        "key1": "abc",
        "key2": "asd",
        "key3": {
            "key4": "abc"
        },
        "key5": {
            "key6": "uvw"
        }
    },
    {
        "key1": "abc",
        "key2": "def",
        "key3": {
            "key4": "ghi"
        },
        "key5": {
            "key6": "uvw"
        }
    },
    {
        "key1": "abc",
        "key2": "dfd",
        "key3": {
            "key4": "ghi"
        },
        "key5": {
            "key6": "ers"
        }
    },
    {
        "key1": "abc",
        "key2": None,
        "key3": None,
        "key5": {
            "key6": "uvw"
        }
    },
    {
        "key1": "xyz",
        "key2": None,
        "key3": {
            "key4": "jklm"
        },
        "key5": {
            "key6": "uvw"
        }
    },
    {
        "key1": None,
        "key2": "asd",
        "key3": {
            "key4": "abc"
        },
        "key5": {
            "key6": "uvw"
        }
    }
]

I tried implementing this using pythons list sort functions and itertools.groupby, but i can’t seem to wrap my head around it.

Thanks in advance!

Asked By: Reinhard

||

Answers:

Try:

def get_values_from_dict(d, keys):
    out = []
    for k in keys:

        root = d
        for kk in k.split("."):
            root = root.get(kk)
            if not root:
                break

        out.append(root)

    return out


def key_function(lst):
    return [(v is None, v) for v in lst]


print(
    sorted(
        dict_list, key=lambda d: key_function(get_values_from_dict(d, groups))
    )
)

Prints:

[
    {
        "key1": "abc",
        "key2": "asd",
        "key3": {"key4": "abc"},
        "key5": {"key6": "uvw"},
    },
    {
        "key1": "abc",
        "key2": "def",
        "key3": {"key4": "ghi"},
        "key5": {"key6": "uvw"},
    },
    {
        "key1": "abc",
        "key2": "dfd",
        "key3": {"key4": "ghi"},
        "key5": {"key6": "ers"},
    },
    {"key1": "abc", "key2": None, "key3": None, "key5": {"key6": "uvw"}},
    {
        "key1": "xyz",
        "key2": None,
        "key3": {"key4": "jklm"},
        "key5": {"key6": "uvw"},
    },
    {
        "key1": None,
        "key2": "asd",
        "key3": {"key4": "abc"},
        "key5": {"key6": "uvw"},
    },
]
Answered By: Andrej Kesely
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.