Skip to content

Commit 14b38ca

Browse files
committed
review comments mostly
1 parent 707b957 commit 14b38ca

File tree

3 files changed

+88
-49
lines changed

3 files changed

+88
-49
lines changed

Doc/library/functools.rst

+23-1
Original file line numberDiff line numberDiff line change
@@ -337,13 +337,33 @@ The :mod:`functools` module defines the following functions:
337337
supplied, they extend and override *keywords*.
338338
Roughly equivalent to::
339339

340+
Placeholder = object()
341+
340342
def partial(func, /, *args, **keywords):
343+
# Trim trailing placeholders and count how many remaining
344+
nargs = len(args)
345+
while nargs and args[nargs-1] is Placeholder:
346+
nargs -= 1
347+
args = args[:nargs]
348+
placeholder_count = args.count(Placeholder)
341349
def newfunc(*fargs, **fkeywords):
350+
if len(fargs) < placeholder_count:
351+
raise TypeError("missing positional arguments in 'partial' call;"
352+
f" expected at least {placeholder_count}, "
353+
f"got {len(fargs)}")
354+
newargs = list(args)
355+
j = 0
356+
for i in range(len(args)):
357+
if args[i] is Placeholder:
358+
newargs[i] = fargs[j]
359+
j += 1
360+
newargs.extend(fargs[j:])
342361
newkeywords = {**keywords, **fkeywords}
343-
return func(*args, *fargs, **newkeywords)
362+
return func(*newargs, **newkeywords)
344363
newfunc.func = func
345364
newfunc.args = args
346365
newfunc.keywords = keywords
366+
newfunc.placeholder_count = placeholder_count
347367
return newfunc
348368

349369
The :func:`partial` is used for partial function application which "freezes"
@@ -365,6 +385,8 @@ The :mod:`functools` module defines the following functions:
365385

366386
>>> from functools import partial, Placeholder
367387
>>> say_to_world = partial(print, Placeholder, 'world!')
388+
>>> say_to_world.placeholder_count
389+
1
368390
>>> say_to_world('Hello')
369391
Hello world!
370392

Lib/functools.py

+23-22
Original file line numberDiff line numberDiff line change
@@ -284,22 +284,22 @@ class partial:
284284
and keywords.
285285
"""
286286

287-
__slots__ = "func", "args", "keywords", "__dict__", "__weakref__"
287+
__slots__ = ("func", "args", "keywords", "placeholder_count",
288+
"__dict__", "__weakref__")
288289

289290
def __new__(cls, func, /, *args, **keywords):
290291
if not callable(func):
291292
raise TypeError("the first argument must be callable")
292-
293-
np = 0
293+
placeholder_count = 0
294294
nargs = len(args)
295295
if args:
296296
while nargs and args[nargs-1] is Placeholder:
297297
nargs -= 1
298298
args = args[:nargs]
299-
np = args.count(Placeholder)
299+
placeholder_count = args.count(Placeholder)
300300
if isinstance(func, partial):
301301
pargs = func.args
302-
pnp = func.np
302+
pnp = func.placeholder_count
303303
if pnp and args:
304304
all_args = list(pargs)
305305
nargs = len(args)
@@ -312,39 +312,37 @@ def __new__(cls, func, /, *args, **keywords):
312312
pos += 1
313313
if pnp < nargs:
314314
all_args.extend(args[pnp:])
315-
np += pnp - end
315+
placeholder_count += pnp - end
316316
args = tuple(all_args)
317317
else:
318-
np += pnp
318+
placeholder_count += pnp
319319
args = func.args + args
320320
keywords = {**func.keywords, **keywords}
321321
func = func.func
322322

323323
self = super(partial, cls).__new__(cls)
324-
325324
self.func = func
326325
self.args = args
327326
self.keywords = keywords
328-
self.np = np
327+
self.placeholder_count = placeholder_count
329328
return self
330329

331330
def __call__(self, /, *args, **keywords):
332-
if not hasattr(self, 'np'):
333-
self.np = self.args.count(Placeholder)
334-
if np := self.np:
335-
if len(args) < np:
336-
raise TypeError("unfilled placeholders in 'partial' call")
331+
keywords = {**self.keywords, **keywords}
332+
if placeholder_count := self.placeholder_count:
333+
if len(args) < placeholder_count:
334+
raise TypeError(
335+
"missing positional arguments in 'partial' call; expected "
336+
f"at least {placeholder_count}, got {len(fargs)}")
337337
f_args = list(self.args)
338338
j, pos = 0, 0
339-
while j < np:
339+
while j < placeholder_count:
340340
pos = f_args.index(Placeholder, pos)
341341
f_args[pos] = args[j]
342342
j += 1
343343
pos += 1
344-
keywords = {**self.keywords, **keywords}
345-
return self.func(*f_args, *args[np:], **keywords)
344+
return self.func(*f_args, *args[j:], **keywords)
346345
else:
347-
keywords = {**self.keywords, **keywords}
348346
return self.func(*self.args, *args, **keywords)
349347

350348
@recursive_repr()
@@ -359,16 +357,18 @@ def __repr__(self):
359357

360358
def __reduce__(self):
361359
return type(self), (self.func,), (self.func, self.args,
362-
self.keywords or None, self.__dict__ or None)
360+
self.keywords or None, self.placeholder_count,
361+
self.__dict__ or None)
363362

364363
def __setstate__(self, state):
365364
if not isinstance(state, tuple):
366365
raise TypeError("argument to __setstate__ must be a tuple")
367-
if len(state) != 4:
368-
raise TypeError(f"expected 4 items in state, got {len(state)}")
369-
func, args, kwds, namespace = state
366+
if len(state) != 5:
367+
raise TypeError(f"expected 5 items in state, got {len(state)}")
368+
func, args, kwds, placeholder_count, namespace = state
370369
if (not callable(func) or not isinstance(args, tuple) or
371370
(kwds is not None and not isinstance(kwds, dict)) or
371+
not isinstance(placeholder_count, int) or
372372
(namespace is not None and not isinstance(namespace, dict))):
373373
raise TypeError("invalid partial state")
374374

@@ -384,6 +384,7 @@ def __setstate__(self, state):
384384
self.func = func
385385
self.args = args
386386
self.keywords = kwds
387+
self.placeholder_count = placeholder_count
387388

388389
try:
389390
from _functools import partial, Placeholder

Modules/_functoolsmodule.c

+42-26
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@ static PyType_Slot placeholder_type_slots[] = {
5757
};
5858

5959
static PyType_Spec placeholder_type_spec = {
60-
.name = "partial.Placeholder",
60+
.name = "functools.Placeholder",
6161
.basicsize = sizeof(placeholderobject),
62-
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
62+
.flags = Py_TPFLAGS_DEFAULT |
6363
Py_TPFLAGS_IMMUTABLETYPE |
6464
Py_TPFLAGS_DISALLOW_INSTANTIATION,
6565
.slots = placeholder_type_slots
66-
};
66+
}; // TODO> test: test.support.check_disallow_instantiation
6767

6868

6969
typedef struct {
@@ -154,25 +154,27 @@ partial_new(PyTypeObject *type, PyObject *args, PyObject *kw)
154154
Py_ssize_t nnp = 0;
155155
Py_ssize_t nnargs = PyTuple_GET_SIZE(nargs);
156156
PyObject *item;
157-
if (nnargs > 0){
157+
if (nnargs > 0) {
158158
/* Trim placeholders from the end if needed */
159159
Py_ssize_t nnargs_old = nnargs;
160160
for (; nnargs > 0; nnargs--) {
161161
item = PyTuple_GET_ITEM(nargs, nnargs-1);
162-
if (!Py_Is(item, pto->placeholder))
162+
if (!Py_Is(item, pto->placeholder)) {
163163
break;
164+
}
164165
}
165166
if (nnargs != nnargs_old) {
166167
PyObject *tmp = PyTuple_GetSlice(nargs, 0, nnargs);
167168
Py_DECREF(nargs);
168169
nargs = tmp;
169170
}
170171
/* Count placeholders */
171-
if (nnargs > 1){
172-
for (Py_ssize_t i=0; i < nnargs - 1; i++){
172+
if (nnargs > 1) {
173+
for (Py_ssize_t i=0; i < nnargs - 1; i++) {
173174
item = PyTuple_GET_ITEM(nargs, i);
174-
if (Py_Is(item, pto->placeholder))
175+
if (Py_Is(item, pto->placeholder)) {
175176
nnp++;
177+
}
176178
}
177179
}
178180
}
@@ -185,12 +187,13 @@ partial_new(PyTypeObject *type, PyObject *args, PyObject *kw)
185187
for (Py_ssize_t i=0, j=0; i < nfargs; i++) {
186188
if (i < npargs) {
187189
item = PyTuple_GET_ITEM(pargs, i);
188-
if ((j < nnargs) & Py_Is(item, pto->placeholder)){
190+
if ((j < nnargs) & Py_Is(item, pto->placeholder)) {
189191
item = PyTuple_GET_ITEM(nargs, j);
190192
j++;
191193
pnp--;
192194
}
193-
} else {
195+
}
196+
else {
194197
item = PyTuple_GET_ITEM(nargs, j);
195198
j++;
196199
}
@@ -200,10 +203,12 @@ partial_new(PyTypeObject *type, PyObject *args, PyObject *kw)
200203
pto->args = fargs;
201204
pto->np = pnp + nnp;
202205
Py_DECREF(nargs);
203-
} else if (pargs == NULL) {
206+
}
207+
else if (pargs == NULL) {
204208
pto->args = nargs;
205209
pto->np = nnp;
206-
} else {
210+
}
211+
else {
207212
pto->args = PySequence_Concat(pargs, nargs);
208213
pto->np = pnp + nnp;
209214
Py_DECREF(nargs);
@@ -306,8 +311,9 @@ partial_vectorcall(partialobject *pto, PyObject *const *args,
306311
}
307312
Py_ssize_t np = pto->np;
308313
if (nargs < np) {
309-
PyErr_SetString(PyExc_TypeError,
310-
"unfilled placeholders in 'partial' call");
314+
PyErr_Format(PyExc_TypeError,
315+
"missing positional arguments in 'partial' call; "
316+
"expected at least %zd, got %zd", np, nargs);
311317
return NULL;
312318
}
313319

@@ -358,19 +364,22 @@ partial_vectorcall(partialobject *pto, PyObject *const *args,
358364
Py_ssize_t nargs_new;
359365
if (np) {
360366
nargs_new = pto_nargs + nargs - np;
361-
Py_ssize_t j = 0; // Placeholder counter
367+
Py_ssize_t j = 0; // New args index
362368
for (Py_ssize_t i=0; i < pto_nargs; i++) {
363369
if (Py_Is(pto_args[i], pto->placeholder)){
364370
memcpy(stack + i, args + j, 1 * sizeof(PyObject*));
365371
j += 1;
366-
} else {
372+
}
373+
else {
367374
memcpy(stack + i, pto_args + i, 1 * sizeof(PyObject*));
368375
}
369376
}
370-
if (nargs_total > np){
371-
memcpy(stack + pto_nargs, args + np, (nargs_total - np) * sizeof(PyObject*));
377+
assert(j == np);
378+
if (nargs_total > np) {
379+
memcpy(stack + pto_nargs, args + j, (nargs_total - j) * sizeof(PyObject*));
372380
}
373-
} else {
381+
}
382+
else {
374383
nargs_new = pto_nargs + nargs;
375384
/* Copy to new stack, using borrowed references */
376385
memcpy(stack, pto_args, pto_nargs * sizeof(PyObject*));
@@ -411,8 +420,9 @@ partial_call(partialobject *pto, PyObject *args, PyObject *kwargs)
411420
Py_ssize_t nargs = PyTuple_GET_SIZE(args);
412421
Py_ssize_t np = pto->np;
413422
if (nargs < np) {
414-
PyErr_SetString(PyExc_TypeError,
415-
"unfilled placeholders in 'partial' call");
423+
PyErr_Format(PyExc_TypeError,
424+
"missing positional arguments in 'partial' call; "
425+
"expected at least %zd, got %zd", np, nargs);
416426
return NULL;
417427
}
418428

@@ -445,25 +455,31 @@ partial_call(partialobject *pto, PyObject *args, PyObject *kwargs)
445455
Py_ssize_t pto_nargs = PyTuple_GET_SIZE(pto->args);
446456
Py_ssize_t nargs_new = pto_nargs + nargs - np;
447457
args2 = PyTuple_New(nargs_new);
458+
if (args2 == NULL) {
459+
Py_XDECREF(kwargs2);
460+
return NULL;
461+
}
448462
PyObject *pto_args = pto->args;
449463
PyObject *item;
450-
Py_ssize_t j = 0; // Placeholder counter
464+
Py_ssize_t j = 0; // New args index
451465
for (Py_ssize_t i=0; i < pto_nargs; i++) {
452466
item = PyTuple_GET_ITEM(pto_args, i);
453-
if (Py_Is(item, pto->placeholder)){
467+
if (Py_Is(item, pto->placeholder)) {
454468
item = PyTuple_GET_ITEM(args, j);
455469
j += 1;
456470
}
457471
PyTuple_SET_ITEM(args2, i, item);
458472
}
459-
if (nargs > np){
473+
assert(j == np);
474+
if (nargs > np) {
460475
for (Py_ssize_t i=pto_nargs; i < nargs_new; i++) {
461476
item = PyTuple_GET_ITEM(args, j);
462477
PyTuple_SET_ITEM(args2, i, item);
463478
j += 1;
464479
}
465480
}
466-
} else {
481+
}
482+
else {
467483
/* Note: tupleconcat() is optimized for empty tuples */
468484
args2 = PySequence_Concat(pto->args, args);
469485
}
@@ -491,7 +507,7 @@ static PyMemberDef partial_memberlist[] = {
491507
"tuple of arguments to future partial calls"},
492508
{"keywords", _Py_T_OBJECT, OFF(kw), Py_READONLY,
493509
"dictionary of keyword arguments to future partial calls"},
494-
{"np", Py_T_PYSSIZET, OFF(np), Py_READONLY,
510+
{"placeholder_count", Py_T_PYSSIZET, OFF(np), Py_READONLY,
495511
"number of placeholders"},
496512
{"__weaklistoffset__", Py_T_PYSSIZET,
497513
offsetof(partialobject, weakreflist), Py_READONLY},

0 commit comments

Comments
 (0)