Allow `sorted` to take list for `key` argument

Well, thinking about it, “hyper-generalisation” does not necessarily seem like a bad idea.
Doing it would provide some convenience for sure.

But there is performance trade-off which I am reluctant to give up.
I will just re-iterate one of key examples.

import random
from operator import itemgetter

def dict_sort(d, by_keys=True):
    # If mutates
    keys = list(d)
    values = list(d.values())
    if by_keys:
        values.sort(keylist=keys)
    else:
        keys.sort(keylist=values)
    # d.clear()
    # d.update(zip(keys, values))

def dict_sort2(d, by_keys=True):
    # If does not mutate
    keys = list(d)
    values = list(d.values())
    keylist = keys if by_keys else values
    # Note, wouldnt need to wrap in list here
    idxs = sorted(range(len(d)), keylist=list(keylist))
    reorderer = itemgetter(*idxs)
    keys = reorderer(keys)
    values = reorderer(values)
    # d.clear()
    # d.update(zip(keys, values))

N = 100_000
keys = random.sample(range(N), N)   # Case 1. Worst
keys = list(range(N))               # Case 2. Best
d = dict(zip(keys, range(N)))

So although this will have less and less impact as number of lists to be reordered grows, the above is the next complexity level, which will be more common case than say re-ordering 3 lists based on 1 keylist. And performance benefit is non-trivial:

./python.exe -m timeit -s "$S" "dict_sort(d)"   # 23 |  2 ms
./python.exe -m timeit -s "$S" "dict_sort2(d)"  # 45 | 10 ms

In short, taking in the final object which gets mutated allows user to take ALL theoretically available opportunities for optimal performance.
Anything else is trading performance in one place or another.

Now I appreciate that maximum performance is not the only measure.
But for this specific case, the cost of extracting it seems low enough that I am very much inclined to mine it properly.

I think leaving 2-5x performance boost (for the 2-list case above) behind without a very good reason would be unfortunate.