Skip to content

Commit 64931d4

Browse files
committed
fix review idea
Signed-off-by: Manjusaka <me@manjusaka.me>
1 parent b69e8dd commit 64931d4

File tree

1 file changed

+146
-136
lines changed

1 file changed

+146
-136
lines changed

Lib/functools.py

Lines changed: 146 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,152 @@ def _partial_repr(self):
365365
args.extend(f"{k}={v!r}" for k, v in self.keywords.items())
366366
return f"{module}.{qualname}({', '.join(args)})"
367367

368+
369+
################################################################################
370+
### _partial_annotate() - compute annotations for partial objects
371+
################################################################################
372+
373+
def _partial_annotate(partial_obj, format):
374+
"""Helper function to compute annotations for a partial object.
375+
376+
This is called by the __annotate__ descriptor defined in C.
377+
Returns annotations for the wrapped function, but only for parameters
378+
that haven't been bound by the partial application.
379+
"""
380+
import inspect
381+
from annotationlib import get_annotations
382+
383+
# Get annotations from the wrapped function
384+
func_annotations = get_annotations(partial_obj.func, format=format)
385+
386+
if not func_annotations:
387+
return {}
388+
389+
# Get the signature to determine which parameters are bound
390+
try:
391+
sig = inspect.signature(partial_obj, annotation_format=format)
392+
except (ValueError, TypeError) as e:
393+
# If we can't get signature, we can't reliably determine which
394+
# parameters are bound. Raise an error rather than returning
395+
# incorrect annotations.
396+
raise TypeError(
397+
f"Cannot compute annotations for {partial_obj!r}: "
398+
f"unable to determine signature"
399+
) from e
400+
401+
# Build new annotations dict with only unbound parameters
402+
# (parameters first, then return)
403+
new_annotations = {}
404+
405+
# Only include annotations for parameters that still exist in partial's signature
406+
for param_name in sig.parameters:
407+
if param_name in func_annotations:
408+
new_annotations[param_name] = func_annotations[param_name]
409+
410+
# Add return annotation at the end
411+
if 'return' in func_annotations:
412+
new_annotations['return'] = func_annotations['return']
413+
414+
return new_annotations
415+
416+
417+
################################################################################
418+
### _partialmethod_annotate() - compute annotations for partialmethod objects
419+
################################################################################
420+
421+
def _partialmethod_annotate(partialmethod_obj, format):
422+
"""Helper function to compute annotations for a partialmethod object.
423+
424+
This is called when accessing annotations on an unbound partialmethod
425+
(via the __partialmethod__ attribute).
426+
Returns annotations for the wrapped function, but only for parameters
427+
that haven't been bound by the partial application. The first parameter
428+
(usually 'self' or 'cls') is kept since partialmethod is unbound.
429+
"""
430+
import inspect
431+
from annotationlib import get_annotations
432+
433+
# Get annotations from the wrapped function
434+
func_annotations = get_annotations(partialmethod_obj.func, format=format)
435+
436+
if not func_annotations:
437+
return {}
438+
439+
# For partialmethod, we need to simulate the signature calculation
440+
# The first parameter (self/cls) should remain, but bound args should be removed
441+
try:
442+
# Get the function signature
443+
func_sig = inspect.signature(partialmethod_obj.func, annotation_format=format)
444+
func_params = list(func_sig.parameters.keys())
445+
446+
if not func_params:
447+
return func_annotations
448+
449+
# Calculate which parameters are bound by the partialmethod
450+
partial_args = partialmethod_obj.args or ()
451+
partial_keywords = partialmethod_obj.keywords or {}
452+
453+
# Build new annotations dict in proper order
454+
# (parameters first, then return)
455+
new_annotations = {}
456+
457+
# The first parameter (self/cls) is always kept for unbound partialmethod
458+
first_param = func_params[0]
459+
if first_param in func_annotations:
460+
new_annotations[first_param] = func_annotations[first_param]
461+
462+
# For partialmethod, positional args bind to parameters AFTER the first one
463+
# So if func is (self, a, b, c) and partialmethod.args=(1,)
464+
# Then 'self' stays, 'a' is bound, 'b' and 'c' remain
465+
466+
# We need to account for Placeholders which create "holes"
467+
# For example: partialmethod(func, 1, Placeholder, 3) binds 'a' and 'c' but not 'b'
468+
469+
remaining_params = func_params[1:]
470+
471+
# Track which positions are filled by Placeholder
472+
placeholder_positions = set()
473+
for i, arg in enumerate(partial_args):
474+
if arg is Placeholder:
475+
placeholder_positions.add(i)
476+
477+
# Number of non-Placeholder positional args
478+
# This doesn't directly tell us which params are bound due to Placeholders
479+
480+
for i, param_name in enumerate(remaining_params):
481+
# Check if this position has a Placeholder
482+
if i in placeholder_positions:
483+
# This parameter is deferred by Placeholder, keep it
484+
if param_name in func_annotations:
485+
new_annotations[param_name] = func_annotations[param_name]
486+
continue
487+
488+
# Check if this position is beyond the partial_args
489+
if i >= len(partial_args):
490+
# This parameter is not bound at all, keep it
491+
if param_name in func_annotations:
492+
new_annotations[param_name] = func_annotations[param_name]
493+
continue
494+
495+
# Otherwise, this position is bound (not a Placeholder and within bounds)
496+
# Skip it
497+
498+
# Add return annotation at the end
499+
if 'return' in func_annotations:
500+
new_annotations['return'] = func_annotations['return']
501+
502+
return new_annotations
503+
504+
except (ValueError, TypeError) as e:
505+
# If we can't process the signature, we can't reliably determine
506+
# which parameters are bound. Raise an error rather than returning
507+
# incorrect annotations (which would include bound parameters).
508+
raise TypeError(
509+
f"Cannot compute annotations for {partialmethod_obj!r}: "
510+
f"unable to determine which parameters are bound"
511+
) from e
512+
513+
368514
# Purely functional, no descriptor behaviour
369515
class partial:
370516
"""New function with partial application of the given arguments
@@ -499,8 +645,6 @@ def __isabstractmethod__(self):
499645
__class_getitem__ = classmethod(GenericAlias)
500646

501647

502-
# Helper functions
503-
504648
def _unwrap_partial(func):
505649
while isinstance(func, partial):
506650
func = func.func
@@ -517,140 +661,6 @@ def _unwrap_partialmethod(func):
517661
func = _unwrap_partial(func)
518662
return func
519663

520-
def _partial_annotate(partial_obj, format):
521-
"""Helper function to compute annotations for a partial object.
522-
523-
This is called by the __annotate__ descriptor defined in C.
524-
Returns annotations for the wrapped function, but only for parameters
525-
that haven't been bound by the partial application.
526-
"""
527-
import inspect
528-
from annotationlib import get_annotations
529-
530-
# Get annotations from the wrapped function
531-
func_annotations = get_annotations(partial_obj.func, format=format)
532-
533-
if not func_annotations:
534-
return {}
535-
536-
# Get the signature to determine which parameters are bound
537-
try:
538-
sig = inspect.signature(partial_obj, annotation_format=format)
539-
except (ValueError, TypeError) as e:
540-
# If we can't get signature, we can't reliably determine which
541-
# parameters are bound. Raise an error rather than returning
542-
# incorrect annotations.
543-
raise TypeError(
544-
f"Cannot compute annotations for {partial_obj!r}: "
545-
f"unable to determine signature"
546-
) from e
547-
548-
# Build new annotations dict with only unbound parameters
549-
# (parameters first, then return)
550-
new_annotations = {}
551-
552-
# Only include annotations for parameters that still exist in partial's signature
553-
for param_name in sig.parameters:
554-
if param_name in func_annotations:
555-
new_annotations[param_name] = func_annotations[param_name]
556-
557-
# Add return annotation at the end
558-
if 'return' in func_annotations:
559-
new_annotations['return'] = func_annotations['return']
560-
561-
return new_annotations
562-
563-
def _partialmethod_annotate(partialmethod_obj, format):
564-
"""Helper function to compute annotations for a partialmethod object.
565-
566-
This is called when accessing annotations on an unbound partialmethod
567-
(via the __partialmethod__ attribute).
568-
Returns annotations for the wrapped function, but only for parameters
569-
that haven't been bound by the partial application. The first parameter
570-
(usually 'self' or 'cls') is kept since partialmethod is unbound.
571-
"""
572-
import inspect
573-
from annotationlib import get_annotations
574-
575-
# Get annotations from the wrapped function
576-
func_annotations = get_annotations(partialmethod_obj.func, format=format)
577-
578-
if not func_annotations:
579-
return {}
580-
581-
# For partialmethod, we need to simulate the signature calculation
582-
# The first parameter (self/cls) should remain, but bound args should be removed
583-
try:
584-
# Get the function signature
585-
func_sig = inspect.signature(partialmethod_obj.func, annotation_format=format)
586-
func_params = list(func_sig.parameters.keys())
587-
588-
if not func_params:
589-
return func_annotations
590-
591-
# Calculate which parameters are bound by the partialmethod
592-
partial_args = partialmethod_obj.args or ()
593-
partial_keywords = partialmethod_obj.keywords or {}
594-
595-
# Build new annotations dict in proper order
596-
# (parameters first, then return)
597-
new_annotations = {}
598-
599-
# The first parameter (self/cls) is always kept for unbound partialmethod
600-
first_param = func_params[0]
601-
if first_param in func_annotations:
602-
new_annotations[first_param] = func_annotations[first_param]
603-
604-
# For partialmethod, positional args bind to parameters AFTER the first one
605-
# So if func is (self, a, b, c) and partialmethod.args=(1,)
606-
# Then 'self' stays, 'a' is bound, 'b' and 'c' remain
607-
608-
# We need to account for Placeholders which create "holes"
609-
# For example: partialmethod(func, 1, Placeholder, 3) binds 'a' and 'c' but not 'b'
610-
611-
remaining_params = func_params[1:]
612-
613-
# Track which positions are filled by Placeholder
614-
placeholder_positions = set()
615-
for i, arg in enumerate(partial_args):
616-
if arg is Placeholder:
617-
placeholder_positions.add(i)
618-
619-
# Number of non-Placeholder positional args
620-
# This doesn't directly tell us which params are bound due to Placeholders
621-
622-
for i, param_name in enumerate(remaining_params):
623-
# Check if this position has a Placeholder
624-
if i in placeholder_positions:
625-
# This parameter is deferred by Placeholder, keep it
626-
if param_name in func_annotations:
627-
new_annotations[param_name] = func_annotations[param_name]
628-
continue
629-
630-
# Check if this position is beyond the partial_args
631-
if i >= len(partial_args):
632-
# This parameter is not bound at all, keep it
633-
if param_name in func_annotations:
634-
new_annotations[param_name] = func_annotations[param_name]
635-
continue
636-
637-
# Otherwise, this position is bound (not a Placeholder and within bounds)
638-
# Skip it
639-
640-
# Add return annotation at the end
641-
if 'return' in func_annotations:
642-
new_annotations['return'] = func_annotations['return']
643-
644-
return new_annotations
645-
646-
except (ValueError, TypeError) as e:
647-
# If we can't process the signature, we can't reliably determine
648-
# which parameters are bound. Raise an error rather than returning
649-
# incorrect annotations (which would include bound parameters).
650-
raise TypeError(
651-
f"Cannot compute annotations for {partialmethod_obj!r}: "
652-
f"unable to determine which parameters are bound"
653-
) from e
654664

655665
################################################################################
656666
### LRU Cache function decorator

0 commit comments

Comments
 (0)