55
66import contextlib
77import functools
8+ import inspect
89import _opcode
910import os
1011import re
@@ -892,8 +893,16 @@ def calcvobjsize(fmt):
892893 return struct .calcsize (_vheader + fmt + _align )
893894
894895
895- _TPFLAGS_HAVE_GC = 1 << 14
896+ _TPFLAGS_STATIC_BUILTIN = 1 << 1
897+ _TPFLAGS_DISALLOW_INSTANTIATION = 1 << 7
898+ _TPFLAGS_IMMUTABLETYPE = 1 << 8
896899_TPFLAGS_HEAPTYPE = 1 << 9
900+ _TPFLAGS_BASETYPE = 1 << 10
901+ _TPFLAGS_READY = 1 << 12
902+ _TPFLAGS_READYING = 1 << 13
903+ _TPFLAGS_HAVE_GC = 1 << 14
904+ _TPFLAGS_BASE_EXC_SUBCLASS = 1 << 30
905+ _TPFLAGS_TYPE_SUBCLASS = 1 << 31
897906
898907def check_sizeof (test , o , size ):
899908 try :
@@ -2608,19 +2617,121 @@ def copy_python_src_ignore(path, names):
26082617 return ignored
26092618
26102619
2611- def iter_builtin_types ():
2612- for obj in __builtins__ .values ():
2613- if not isinstance (obj , type ):
2620+ # XXX Move this to the inspect module?
2621+ def walk_class_hierarchy (top , * , topdown = True ):
2622+ # This is based on the logic in os.walk().
2623+ assert isinstance (top , type ), repr (top )
2624+ stack = [top ]
2625+ while stack :
2626+ top = stack .pop ()
2627+ if isinstance (top , tuple ):
2628+ yield top
26142629 continue
2615- cls = obj
2616- if cls .__module__ != 'builtins' :
2630+
2631+ subs = type (top ).__subclasses__ (top )
2632+ if topdown :
2633+ # Yield before subclass traversal if going top down.
2634+ yield top , subs
2635+ # Traverse into subclasses.
2636+ for sub in reversed (subs ):
2637+ stack .append (sub )
2638+ else :
2639+ # Yield after subclass traversal if going bottom up.
2640+ stack .append ((top , subs ))
2641+ # Traverse into subclasses.
2642+ for sub in reversed (subs ):
2643+ stack .append (sub )
2644+
2645+
2646+ def iter_builtin_types ():
2647+ # First try the explicit route.
2648+ try :
2649+ import _testinternalcapi
2650+ except ImportError :
2651+ _testinternalcapi = None
2652+ if _testinternalcapi is not None :
2653+ yield from _testinternalcapi .get_static_builtin_types ()
2654+ return
2655+
2656+ # Fall back to making a best-effort guess.
2657+ if hasattr (object , '__flags__' ):
2658+ # Look for any type object with the Py_TPFLAGS_STATIC_BUILTIN flag set.
2659+ import datetime
2660+ seen = set ()
2661+ for cls , subs in walk_class_hierarchy (object ):
2662+ if cls in seen :
2663+ continue
2664+ seen .add (cls )
2665+ if not (cls .__flags__ & _TPFLAGS_STATIC_BUILTIN ):
2666+ # Do not walk its subclasses.
2667+ subs [:] = []
2668+ continue
2669+ yield cls
2670+ else :
2671+ # Fall back to a naive approach.
2672+ seen = set ()
2673+ for obj in __builtins__ .values ():
2674+ if not isinstance (obj , type ):
2675+ continue
2676+ cls = obj
2677+ # XXX?
2678+ if cls .__module__ != 'builtins' :
2679+ continue
2680+ if cls == ExceptionGroup :
2681+ # It's a heap type.
2682+ continue
2683+ if cls in seen :
2684+ continue
2685+ seen .add (cls )
2686+ yield cls
2687+
2688+
2689+ # XXX Move this to the inspect module?
2690+ def iter_name_in_mro (cls , name ):
2691+ """Yield matching items found in base.__dict__ across the MRO.
2692+
2693+ The descriptor protocol is not invoked.
2694+
2695+ list(iter_name_in_mro(cls, name))[0] is roughly equivalent to
2696+ find_name_in_mro() in Objects/typeobject.c (AKA PyType_Lookup()).
2697+
2698+ inspect.getattr_static() is similar.
2699+ """
2700+ # This can fail if "cls" is weird.
2701+ for base in inspect ._static_getmro (cls ):
2702+ # This can fail if "base" is weird.
2703+ ns = inspect ._get_dunder_dict_of_class (base )
2704+ try :
2705+ obj = ns [name ]
2706+ except KeyError :
26172707 continue
2618- yield cls
2708+ yield obj , base
26192709
26202710
2621- def iter_slot_wrappers (cls ):
2622- assert cls .__module__ == 'builtins' , cls
2711+ # XXX Move this to the inspect module?
2712+ def find_name_in_mro (cls , name , default = inspect ._sentinel ):
2713+ for res in iter_name_in_mro (cls , name ):
2714+ # Return the first one.
2715+ return res
2716+ if default is not inspect ._sentinel :
2717+ return default , None
2718+ raise AttributeError (name )
2719+
2720+
2721+ # XXX The return value should always be exactly the same...
2722+ def identify_type_slot_wrappers ():
2723+ try :
2724+ import _testinternalcapi
2725+ except ImportError :
2726+ _testinternalcapi = None
2727+ if _testinternalcapi is not None :
2728+ names = {n : None for n in _testinternalcapi .identify_type_slot_wrappers ()}
2729+ return list (names )
2730+ else :
2731+ raise NotImplementedError
2732+
26232733
2734+ def iter_slot_wrappers (cls ):
26242735 def is_slot_wrapper (name , value ):
26252736 if not isinstance (value , types .WrapperDescriptorType ):
26262737 assert not repr (value ).startswith ('<slot wrapper ' ), (cls , name , value )
@@ -2630,6 +2741,19 @@ def is_slot_wrapper(name, value):
26302741 assert name .startswith ('__' ) and name .endswith ('__' ), (cls , name , value )
26312742 return True
26322743
2744+ try :
2745+ attrs = identify_type_slot_wrappers ()
2746+ except NotImplementedError :
2747+ attrs = None
2748+ if attrs is not None :
2749+ for attr in sorted (attrs ):
2750+ obj , base = find_name_in_mro (cls , attr , None )
2751+ if obj is not None and is_slot_wrapper (attr , obj ):
2752+ yield attr , base is cls
2753+ return
2754+
2755+ # Fall back to a naive best-effort approach.
2756+
26332757 ns = vars (cls )
26342758 unused = set (ns )
26352759 for name in dir (cls ):
0 commit comments