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.