Skip to content

Commit fd16189

Browse files
committed
CR part 1
1 parent 3c872bd commit fd16189

File tree

4 files changed

+78
-22
lines changed

4 files changed

+78
-22
lines changed

Lib/functools.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ def _partial_prepare_merger(args):
304304
nargs = len(args)
305305
if not nargs:
306306
return 0, None
307-
order = list()
307+
order = []
308308
i, j = 0, nargs
309309
for a in args:
310310
if a is Placeholder:
@@ -319,20 +319,18 @@ def _partial_prepare_merger(args):
319319

320320
def _partial_new(cls, func, /, *args, **keywords):
321321
if issubclass(cls, partial):
322+
base_cls = partial
322323
if not callable(func):
323324
raise TypeError("the first argument must be callable")
324325
else:
326+
base_cls = partialmethod
327+
# func could be a descriptor like classmethod which isn't callable
325328
# assert issubclass(cls, partialmethod)
326329
if not callable(func) and not hasattr(func, "__get__"):
327330
raise TypeError(f"{func!r} is not callable or a descriptor")
328-
# func could be a descriptor like classmethod which isn't callable,
329-
# so we can't inherit from partial (it verifies func is callable)
330-
# flattening is mandatory in order to place cls/self before all
331-
# other arguments
332-
# it's also more efficient since only one function will be called
333331
if args and args[-1] is Placeholder:
334332
raise TypeError("trailing Placeholders are not allowed")
335-
if isinstance(func, cls):
333+
if isinstance(func, base_cls):
336334
pto_phcount = func._phcount
337335
tot_args = func.args
338336
if args:
@@ -346,7 +344,7 @@ def _partial_new(cls, func, /, *args, **keywords):
346344
if nargs > pto_phcount:
347345
tot_args += args[pto_phcount:]
348346
phcount, merger = _partial_prepare_merger(tot_args)
349-
elif pto_phcount: # not args
347+
elif pto_phcount: # not args and pto_phcount
350348
phcount, merger = pto_phcount, func._merger
351349
else: # not args and not pto_phcount
352350
phcount, merger = 0, None
@@ -386,7 +384,8 @@ class partial:
386384
__repr__ = recursive_repr()(_partial_repr)
387385

388386
def __call__(self, /, *args, **keywords):
389-
if phcount := self._phcount:
387+
phcount = self._phcount
388+
if phcount:
390389
try:
391390
pto_args = self._merger(self.args + args)
392391
args = args[phcount:]
@@ -456,7 +455,8 @@ class partialmethod:
456455

457456
def _make_unbound_method(self):
458457
def _method(cls_or_self, /, *args, **keywords):
459-
if phcount := self._phcount:
458+
phcount = self._phcount
459+
if phcount:
460460
try:
461461
pto_args = self._merger(self.args + args)
462462
args = args[phcount:]

Lib/test/test_functools.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,19 @@ def __str__(self):
510510
self.assertIn('astr', r)
511511
self.assertIn("['sth']", r)
512512

513+
def test_placeholders_refcount_smoke(self):
514+
PH = self.module.Placeholder
515+
# sum supports vector call
516+
lst1, start = [], []
517+
sum_lists = self.partial(sum, PH, start)
518+
for i in range(10):
519+
sum_lists([lst1, lst1])
520+
# collections.ChainMap initializer does not support vectorcall
521+
map1, map2 = {}, {}
522+
partial_cm = self.partial(collections.ChainMap, PH, map1)
523+
for i in range(10):
524+
partial_cm(map2, map2)
525+
513526

514527
class TestPartialPy(TestPartial, unittest.TestCase):
515528
module = py_functools
@@ -534,6 +547,13 @@ class TestPartialCSubclass(TestPartialC):
534547
class TestPartialPySubclass(TestPartialPy):
535548
partial = PyPartialSubclass
536549

550+
def test_subclass_optimization(self):
551+
p = py_functools.partial(min, 2)
552+
p2 = self.partial(p, 1)
553+
assert p2.func == min
554+
assert p2(0) == 0
555+
556+
537557
class TestPartialMethod(unittest.TestCase):
538558

539559
class A(object):
@@ -671,6 +691,14 @@ def f(a, b, /):
671691
p = functools.partial(f, 1)
672692
self.assertEqual(p(2), f(1, 2))
673693

694+
def test_subclass_optimization(self):
695+
class PartialMethodSubclass(functools.partialmethod):
696+
pass
697+
p = functools.partialmethod(min, 2)
698+
p2 = PartialMethodSubclass(p, 1)
699+
assert p2.func == min
700+
assert p2.__get__(0)() == 0
701+
674702

675703
class TestUpdateWrapper(unittest.TestCase):
676704

Lib/test/test_inspect/test_inspect.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3507,6 +3507,34 @@ def foo(a, b, /, c, d, **kwargs):
35073507
('kwargs', ..., ..., 'var_keyword')),
35083508
...))
35093509

3510+
# Positional only With Placeholder
3511+
p = partial(foo, Placeholder, 1, c=0, d=1)
3512+
self.assertEqual(self.signature(p),
3513+
((('a', ..., ..., "positional_only"),
3514+
('c', 0, ..., "keyword_only"),
3515+
('d', 1, ..., "keyword_only"),
3516+
('kwargs', ..., ..., "var_keyword")),
3517+
...))
3518+
3519+
# Optionals Positional With Placeholder
3520+
def foo(a=0, b=1, /, c=2, d=3):
3521+
pass
3522+
3523+
# Positional
3524+
p = partial(foo, Placeholder, 1, c=0, d=1)
3525+
self.assertEqual(self.signature(p),
3526+
((('a', 0, ..., "positional_only"),
3527+
('c', 0, ..., "keyword_only"),
3528+
('d', 1, ..., "keyword_only")),
3529+
...))
3530+
3531+
# Positional or Keyword - transformed to positional
3532+
p = partial(foo, Placeholder, 1, Placeholder, 1)
3533+
self.assertEqual(self.signature(p),
3534+
((('a', 0, ..., "positional_only"),
3535+
('c', 2, ..., "positional_only")),
3536+
...))
3537+
35103538
def test_signature_on_partialmethod(self):
35113539
from functools import partialmethod
35123540

Modules/_functoolsmodule.c

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ partial_new(PyTypeObject *type, PyObject *args, PyObject *kw)
194194
pto->fn = Py_NewRef(func);
195195

196196
pto->placeholder = state->placeholder;
197-
if (new_nargs && Py_Is(PyTuple_GET_ITEM(args, new_nargs), pto->placeholder)) {
197+
if (new_nargs && PyTuple_GET_ITEM(args, new_nargs) == pto->placeholder) {
198198
PyErr_SetString(PyExc_TypeError,
199199
"trailing Placeholders are not allowed");
200200
return NULL;
@@ -214,7 +214,7 @@ partial_new(PyTypeObject *type, PyObject *args, PyObject *kw)
214214
}
215215
}
216216
/* merge args with args of `func` which is `partial` */
217-
if ((pto_phcount > 0) && (new_nargs > 0)) {
217+
if (pto_phcount > 0 && new_nargs > 0) {
218218
Py_ssize_t npargs = PyTuple_GET_SIZE(pto_args);
219219
Py_ssize_t tot_nargs = npargs;
220220
if (new_nargs > pto_phcount) {
@@ -409,7 +409,7 @@ partial_vectorcall(partialobject *pto, PyObject *const *args,
409409
tot_nargs = pto_nargs + nargs - pto_phcount;
410410
Py_ssize_t j = 0; // New args index
411411
for (Py_ssize_t i = 0; i < pto_nargs; i++) {
412-
if (Py_Is(pto_args[i], pto->placeholder)){
412+
if (pto_args[i] == pto->placeholder){
413413
stack[i] = args[j];
414414
j += 1;
415415
}
@@ -508,19 +508,19 @@ partial_call(partialobject *pto, PyObject *args, PyObject *kwargs)
508508
Py_ssize_t j = 0; // New args index
509509
for (Py_ssize_t i = 0; i < pto_nargs; i++) {
510510
item = PyTuple_GET_ITEM(pto_args, i);
511-
if (Py_Is(item, pto->placeholder)) {
511+
if (item == pto->placeholder) {
512512
item = PyTuple_GET_ITEM(args, j);
513513
j += 1;
514514
}
515+
Py_INCREF(item);
515516
PyTuple_SET_ITEM(tot_args, i, item);
516517
}
517518
assert(j == pto_phcount);
518-
if (nargs > pto_phcount) {
519-
for (Py_ssize_t i = pto_nargs; i < tot_nargs; i++) {
520-
item = PyTuple_GET_ITEM(args, j);
521-
PyTuple_SET_ITEM(tot_args, i, item);
522-
j += 1;
523-
}
519+
for (Py_ssize_t i = pto_nargs; i < tot_nargs; i++) {
520+
item = PyTuple_GET_ITEM(args, j);
521+
Py_INCREF(item);
522+
PyTuple_SET_ITEM(tot_args, i, item);
523+
j += 1;
524524
}
525525
}
526526
else {
@@ -670,15 +670,15 @@ partial_setstate(partialobject *pto, PyObject *state)
670670
}
671671

672672
Py_ssize_t nargs = PyTuple_GET_SIZE(fnargs);
673-
if (nargs && Py_Is(PyTuple_GET_ITEM(fnargs, nargs - 1), pto->placeholder)) {
673+
if (nargs && PyTuple_GET_ITEM(fnargs, nargs - 1) == pto->placeholder) {
674674
PyErr_SetString(PyExc_TypeError,
675675
"trailing Placeholders are not allowed");
676676
return NULL;
677677
}
678678
/* Count placeholders */
679679
Py_ssize_t phcount = 0;
680680
for (Py_ssize_t i = 0; i < nargs - 1; i++) {
681-
if (Py_Is(PyTuple_GET_ITEM(fnargs, i), pto->placeholder)) {
681+
if (PyTuple_GET_ITEM(fnargs, i), pto->placeholder) {
682682
phcount++;
683683
}
684684
}

0 commit comments

Comments
 (0)