6
6
# Written by Nick Coghlan <ncoghlan at gmail.com>,
7
7
# Raymond Hettinger <python at rcn.com>,
8
8
# and Łukasz Langa <lukasz at langa.pl>.
9
- # Copyright (C) 2006-2013 Python Software Foundation.
9
+ # Copyright (C) 2006-2024 Python Software Foundation.
10
10
# See C source code for _functools credits/copyright
11
11
12
12
__all__ = ['update_wrapper' , 'wraps' , 'WRAPPER_ASSIGNMENTS' , 'WRAPPER_UPDATES' ,
13
13
'total_ordering' , 'cache' , 'cmp_to_key' , 'lru_cache' , 'reduce' ,
14
14
'partial' , 'partialmethod' , 'singledispatch' , 'singledispatchmethod' ,
15
- 'cached_property' ]
15
+ 'cached_property' , 'Placeholder' ]
16
16
17
17
from abc import get_cache_token
18
18
from collections import namedtuple
19
19
# import types, weakref # Deferred to single_dispatch()
20
+ from operator import itemgetter
20
21
from reprlib import recursive_repr
21
22
from types import MethodType
22
23
from _thread import RLock
@@ -274,43 +275,125 @@ def reduce(function, sequence, initial=_initial_missing):
274
275
### partial() argument application
275
276
################################################################################
276
277
277
- # Purely functional, no descriptor behaviour
278
- class partial :
279
- """New function with partial application of the given arguments
280
- and keywords.
278
+
279
+ class _PlaceholderType :
280
+ """The type of the Placeholder singleton.
281
+
282
+ Used as a placeholder for partial arguments.
281
283
"""
284
+ __instance = None
285
+ __slots__ = ()
286
+
287
+ def __init_subclass__ (cls , * args , ** kwargs ):
288
+ raise TypeError (f"type '{ cls .__name__ } ' is not an acceptable base type" )
282
289
283
- __slots__ = "func" , "args" , "keywords" , "__dict__" , "__weakref__"
290
+ def __new__ (cls ):
291
+ if cls .__instance is None :
292
+ cls .__instance = object .__new__ (cls )
293
+ return cls .__instance
294
+
295
+ def __repr__ (self ):
296
+ return 'Placeholder'
284
297
285
- def __new__ (cls , func , / , * args , ** keywords ):
298
+ def __reduce__ (self ):
299
+ return 'Placeholder'
300
+
301
+ Placeholder = _PlaceholderType ()
302
+
303
+ def _partial_prepare_merger (args ):
304
+ if not args :
305
+ return 0 , None
306
+ nargs = len (args )
307
+ order = []
308
+ j = nargs
309
+ for i , a in enumerate (args ):
310
+ if a is Placeholder :
311
+ order .append (j )
312
+ j += 1
313
+ else :
314
+ order .append (i )
315
+ phcount = j - nargs
316
+ merger = itemgetter (* order ) if phcount else None
317
+ return phcount , merger
318
+
319
+ def _partial_new (cls , func , / , * args , ** keywords ):
320
+ if issubclass (cls , partial ):
321
+ base_cls = partial
286
322
if not callable (func ):
287
323
raise TypeError ("the first argument must be callable" )
324
+ else :
325
+ base_cls = partialmethod
326
+ # func could be a descriptor like classmethod which isn't callable
327
+ if not callable (func ) and not hasattr (func , "__get__" ):
328
+ raise TypeError (f"the first argument { func !r} must be a callable "
329
+ "or a descriptor" )
330
+ if args and args [- 1 ] is Placeholder :
331
+ raise TypeError ("trailing Placeholders are not allowed" )
332
+ if isinstance (func , base_cls ):
333
+ pto_phcount = func ._phcount
334
+ tot_args = func .args
335
+ if args :
336
+ tot_args += args
337
+ if pto_phcount :
338
+ # merge args with args of `func` which is `partial`
339
+ nargs = len (args )
340
+ if nargs < pto_phcount :
341
+ tot_args += (Placeholder ,) * (pto_phcount - nargs )
342
+ tot_args = func ._merger (tot_args )
343
+ if nargs > pto_phcount :
344
+ tot_args += args [pto_phcount :]
345
+ phcount , merger = _partial_prepare_merger (tot_args )
346
+ else : # works for both pto_phcount == 0 and != 0
347
+ phcount , merger = pto_phcount , func ._merger
348
+ keywords = {** func .keywords , ** keywords }
349
+ func = func .func
350
+ else :
351
+ tot_args = args
352
+ phcount , merger = _partial_prepare_merger (tot_args )
353
+
354
+ self = object .__new__ (cls )
355
+ self .func = func
356
+ self .args = tot_args
357
+ self .keywords = keywords
358
+ self ._phcount = phcount
359
+ self ._merger = merger
360
+ return self
361
+
362
+ def _partial_repr (self ):
363
+ cls = type (self )
364
+ module = cls .__module__
365
+ qualname = cls .__qualname__
366
+ args = [repr (self .func )]
367
+ args .extend (map (repr , self .args ))
368
+ args .extend (f"{ k } ={ v !r} " for k , v in self .keywords .items ())
369
+ return f"{ module } .{ qualname } ({ ', ' .join (args )} )"
288
370
289
- if isinstance (func , partial ):
290
- args = func .args + args
291
- keywords = {** func .keywords , ** keywords }
292
- func = func .func
371
+ # Purely functional, no descriptor behaviour
372
+ class partial :
373
+ """New function with partial application of the given arguments
374
+ and keywords.
375
+ """
293
376
294
- self = super (partial , cls ).__new__ (cls )
377
+ __slots__ = ("func" , "args" , "keywords" , "_phcount" , "_merger" ,
378
+ "__dict__" , "__weakref__" )
295
379
296
- self .func = func
297
- self .args = args
298
- self .keywords = keywords
299
- return self
380
+ __new__ = _partial_new
381
+ __repr__ = recursive_repr ()(_partial_repr )
300
382
301
383
def __call__ (self , / , * args , ** keywords ):
384
+ phcount = self ._phcount
385
+ if phcount :
386
+ try :
387
+ pto_args = self ._merger (self .args + args )
388
+ args = args [phcount :]
389
+ except IndexError :
390
+ raise TypeError ("missing positional arguments "
391
+ "in 'partial' call; expected "
392
+ f"at least { phcount } , got { len (args )} " )
393
+ else :
394
+ pto_args = self .args
302
395
keywords = {** self .keywords , ** keywords }
303
- return self .func (* self .args , * args , ** keywords )
304
-
305
- @recursive_repr ()
306
- def __repr__ (self ):
307
- cls = type (self )
308
- qualname = cls .__qualname__
309
- module = cls .__module__
310
- args = [repr (self .func )]
311
- args .extend (repr (x ) for x in self .args )
312
- args .extend (f"{ k } ={ v !r} " for (k , v ) in self .keywords .items ())
313
- return f"{ module } .{ qualname } ({ ', ' .join (args )} )"
396
+ return self .func (* pto_args , * args , ** keywords )
314
397
315
398
def __get__ (self , obj , objtype = None ):
316
399
if obj is None :
@@ -332,6 +415,10 @@ def __setstate__(self, state):
332
415
(namespace is not None and not isinstance (namespace , dict ))):
333
416
raise TypeError ("invalid partial state" )
334
417
418
+ if args and args [- 1 ] is Placeholder :
419
+ raise TypeError ("trailing Placeholders are not allowed" )
420
+ phcount , merger = _partial_prepare_merger (args )
421
+
335
422
args = tuple (args ) # just in case it's a subclass
336
423
if kwds is None :
337
424
kwds = {}
@@ -344,53 +431,40 @@ def __setstate__(self, state):
344
431
self .func = func
345
432
self .args = args
346
433
self .keywords = kwds
434
+ self ._phcount = phcount
435
+ self ._merger = merger
347
436
348
437
try :
349
- from _functools import partial
438
+ from _functools import partial , Placeholder , _PlaceholderType
350
439
except ImportError :
351
440
pass
352
441
353
442
# Descriptor version
354
- class partialmethod ( object ) :
443
+ class partialmethod :
355
444
"""Method descriptor with partial application of the given arguments
356
445
and keywords.
357
446
358
447
Supports wrapping existing descriptors and handles non-descriptor
359
448
callables as instance methods.
360
449
"""
361
-
362
- def __init__ (self , func , / , * args , ** keywords ):
363
- if not callable (func ) and not hasattr (func , "__get__" ):
364
- raise TypeError ("{!r} is not callable or a descriptor"
365
- .format (func ))
366
-
367
- # func could be a descriptor like classmethod which isn't callable,
368
- # so we can't inherit from partial (it verifies func is callable)
369
- if isinstance (func , partialmethod ):
370
- # flattening is mandatory in order to place cls/self before all
371
- # other arguments
372
- # it's also more efficient since only one function will be called
373
- self .func = func .func
374
- self .args = func .args + args
375
- self .keywords = {** func .keywords , ** keywords }
376
- else :
377
- self .func = func
378
- self .args = args
379
- self .keywords = keywords
380
-
381
- def __repr__ (self ):
382
- cls = type (self )
383
- module = cls .__module__
384
- qualname = cls .__qualname__
385
- args = [repr (self .func )]
386
- args .extend (map (repr , self .args ))
387
- args .extend (f"{ k } ={ v !r} " for k , v in self .keywords .items ())
388
- return f"{ module } .{ qualname } ({ ', ' .join (args )} )"
450
+ __new__ = _partial_new
451
+ __repr__ = _partial_repr
389
452
390
453
def _make_unbound_method (self ):
391
454
def _method (cls_or_self , / , * args , ** keywords ):
455
+ phcount = self ._phcount
456
+ if phcount :
457
+ try :
458
+ pto_args = self ._merger (self .args + args )
459
+ args = args [phcount :]
460
+ except IndexError :
461
+ raise TypeError ("missing positional arguments "
462
+ "in 'partialmethod' call; expected "
463
+ f"at least { phcount } , got { len (args )} " )
464
+ else :
465
+ pto_args = self .args
392
466
keywords = {** self .keywords , ** keywords }
393
- return self .func (cls_or_self , * self . args , * args , ** keywords )
467
+ return self .func (cls_or_self , * pto_args , * args , ** keywords )
394
468
_method .__isabstractmethod__ = self .__isabstractmethod__
395
469
_method .__partialmethod__ = self
396
470
return _method
0 commit comments