Skip to content

Commit 4ee3914

Browse files
committed
Issue #29203: functools.lru_cache() now respects PEP 468
1 parent 04316c4 commit 4ee3914

4 files changed

Lines changed: 35 additions & 35 deletions

File tree

Lib/functools.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ def __hash__(self):
421421
def _make_key(args, kwds, typed,
422422
kwd_mark = (object(),),
423423
fasttypes = {int, str, frozenset, type(None)},
424-
sorted=sorted, tuple=tuple, type=type, len=len):
424+
tuple=tuple, type=type, len=len):
425425
"""Make a cache key from optionally typed positional and keyword arguments
426426
427427
The key is constructed in a way that is flat as possible rather than
@@ -434,14 +434,13 @@ def _make_key(args, kwds, typed,
434434
"""
435435
key = args
436436
if kwds:
437-
sorted_items = sorted(kwds.items())
438437
key += kwd_mark
439-
for item in sorted_items:
438+
for item in kwds.items():
440439
key += item
441440
if typed:
442441
key += tuple(type(v) for v in args)
443442
if kwds:
444-
key += tuple(type(v) for k, v in sorted_items)
443+
key += tuple(type(v) for v in kwds.values())
445444
elif len(key) == 1 and type(key[0]) in fasttypes:
446445
return key[0]
447446
return _HashedSeq(key)

Lib/test/test_functools.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,6 +1306,16 @@ def fib(n):
13061306
self.assertEqual(fib.cache_info(),
13071307
self.module._CacheInfo(hits=0, misses=0, maxsize=None, currsize=0))
13081308

1309+
def test_kwargs_order(self):
1310+
# PEP 468: Preserving Keyword Argument Order
1311+
@self.module.lru_cache(maxsize=10)
1312+
def f(**kwargs):
1313+
return list(kwargs.items())
1314+
self.assertEqual(f(a=1, b=2), [('a', 1), ('b', 2)])
1315+
self.assertEqual(f(b=2, a=1), [('b', 2), ('a', 1)])
1316+
self.assertEqual(f.cache_info(),
1317+
self.module._CacheInfo(hits=0, misses=2, maxsize=10, currsize=2))
1318+
13091319
def test_lru_cache_decoration(self):
13101320
def f(zomg: 'zomg_annotation'):
13111321
"""f doc string"""

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ Library
4747
- Issue #28961: Fix unittest.mock._Call helper: don't ignore the name parameter
4848
anymore. Patch written by Jiajun Huang.
4949

50+
- Issue #29203: functools.lru_cache() now respects PEP 468 and preserves
51+
the order of keyword arguments. f(a=1, b=2) is now cached separately
52+
from f(b=2, a=1) since both calls could potentially give different results.
53+
5054
- Issue #15812: inspect.getframeinfo() now correctly shows the first line of
5155
a context. Patch by Sam Breese.
5256

Modules/_functoolsmodule.c

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -704,73 +704,60 @@ static PyTypeObject lru_cache_type;
704704
static PyObject *
705705
lru_cache_make_key(PyObject *args, PyObject *kwds, int typed)
706706
{
707-
PyObject *key, *sorted_items;
708-
Py_ssize_t key_size, pos, key_pos;
707+
PyObject *key, *keyword, *value;
708+
Py_ssize_t key_size, pos, key_pos, kwds_size;
709709

710710
/* short path, key will match args anyway, which is a tuple */
711711
if (!typed && !kwds) {
712712
Py_INCREF(args);
713713
return args;
714714
}
715715

716-
if (kwds && PyDict_Size(kwds) > 0) {
717-
sorted_items = PyDict_Items(kwds);
718-
if (!sorted_items)
719-
return NULL;
720-
if (PyList_Sort(sorted_items) < 0) {
721-
Py_DECREF(sorted_items);
722-
return NULL;
723-
}
724-
} else
725-
sorted_items = NULL;
716+
kwds_size = kwds ? PyDict_Size(kwds) : 0;
717+
assert(kwds_size >= 0);
726718

727719
key_size = PyTuple_GET_SIZE(args);
728-
if (sorted_items)
729-
key_size += PyList_GET_SIZE(sorted_items);
720+
if (kwds_size)
721+
key_size += kwds_size * 2 + 1;
730722
if (typed)
731-
key_size *= 2;
732-
if (sorted_items)
733-
key_size++;
723+
key_size += PyTuple_GET_SIZE(args) + kwds_size;
734724

735725
key = PyTuple_New(key_size);
736726
if (key == NULL)
737-
goto done;
727+
return NULL;
738728

739729
key_pos = 0;
740730
for (pos = 0; pos < PyTuple_GET_SIZE(args); ++pos) {
741731
PyObject *item = PyTuple_GET_ITEM(args, pos);
742732
Py_INCREF(item);
743733
PyTuple_SET_ITEM(key, key_pos++, item);
744734
}
745-
if (sorted_items) {
735+
if (kwds_size) {
746736
Py_INCREF(kwd_mark);
747737
PyTuple_SET_ITEM(key, key_pos++, kwd_mark);
748-
for (pos = 0; pos < PyList_GET_SIZE(sorted_items); ++pos) {
749-
PyObject *item = PyList_GET_ITEM(sorted_items, pos);
750-
Py_INCREF(item);
751-
PyTuple_SET_ITEM(key, key_pos++, item);
738+
for (pos = 0; PyDict_Next(kwds, &pos, &keyword, &value);) {
739+
Py_INCREF(keyword);
740+
PyTuple_SET_ITEM(key, key_pos++, keyword);
741+
Py_INCREF(value);
742+
PyTuple_SET_ITEM(key, key_pos++, value);
752743
}
744+
assert(key_pos == PyTuple_GET_SIZE(args) + kwds_size * 2 + 1);
753745
}
754746
if (typed) {
755747
for (pos = 0; pos < PyTuple_GET_SIZE(args); ++pos) {
756748
PyObject *item = (PyObject *)Py_TYPE(PyTuple_GET_ITEM(args, pos));
757749
Py_INCREF(item);
758750
PyTuple_SET_ITEM(key, key_pos++, item);
759751
}
760-
if (sorted_items) {
761-
for (pos = 0; pos < PyList_GET_SIZE(sorted_items); ++pos) {
762-
PyObject *tp_items = PyList_GET_ITEM(sorted_items, pos);
763-
PyObject *item = (PyObject *)Py_TYPE(PyTuple_GET_ITEM(tp_items, 1));
752+
if (kwds_size) {
753+
for (pos = 0; PyDict_Next(kwds, &pos, &keyword, &value);) {
754+
PyObject *item = (PyObject *)Py_TYPE(value);
764755
Py_INCREF(item);
765756
PyTuple_SET_ITEM(key, key_pos++, item);
766757
}
767758
}
768759
}
769760
assert(key_pos == key_size);
770-
771-
done:
772-
if (sorted_items)
773-
Py_DECREF(sorted_items);
774761
return key;
775762
}
776763

0 commit comments

Comments
 (0)