| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101 |
- """Find all objects reachable from a root object."""
- from __future__ import annotations
- import types
- import weakref
- from collections.abc import Iterable
- from typing import Final, Iterator, Mapping
- method_descriptor_type: Final = type(object.__dir__)
- method_wrapper_type: Final = type(object().__ne__)
- wrapper_descriptor_type: Final = type(object.__ne__)
- FUNCTION_TYPES: Final = (
- types.BuiltinFunctionType,
- types.FunctionType,
- types.MethodType,
- method_descriptor_type,
- wrapper_descriptor_type,
- method_wrapper_type,
- )
- ATTR_BLACKLIST: Final = {"__doc__", "__name__", "__class__", "__dict__"}
- # Instances of these types can't have references to other objects
- ATOMIC_TYPE_BLACKLIST: Final = {bool, int, float, str, type(None), object}
- # Don't look at most attributes of these types
- COLLECTION_TYPE_BLACKLIST: Final = {list, set, dict, tuple}
- # Don't return these objects
- TYPE_BLACKLIST: Final = {weakref.ReferenceType}
- def isproperty(o: object, attr: str) -> bool:
- return isinstance(getattr(type(o), attr, None), property)
- def get_edge_candidates(o: object) -> Iterator[tuple[object, object]]:
- # use getattr because mypyc expects dict, not mappingproxy
- if "__getattribute__" in getattr(type(o), "__dict__"): # noqa: B009
- return
- if type(o) not in COLLECTION_TYPE_BLACKLIST:
- for attr in dir(o):
- try:
- if attr not in ATTR_BLACKLIST and hasattr(o, attr) and not isproperty(o, attr):
- e = getattr(o, attr)
- if type(e) not in ATOMIC_TYPE_BLACKLIST:
- yield attr, e
- except AssertionError:
- pass
- if isinstance(o, Mapping):
- yield from o.items()
- elif isinstance(o, Iterable) and not isinstance(o, str):
- for i, e in enumerate(o):
- yield i, e
- def get_edges(o: object) -> Iterator[tuple[object, object]]:
- for s, e in get_edge_candidates(o):
- if isinstance(e, FUNCTION_TYPES):
- # We don't want to collect methods, but do want to collect values
- # in closures and self pointers to other objects
- if hasattr(e, "__closure__"):
- yield (s, "__closure__"), e.__closure__
- if hasattr(e, "__self__"):
- se = e.__self__
- if se is not o and se is not type(o) and hasattr(s, "__self__"):
- yield s.__self__, se
- else:
- if type(e) not in TYPE_BLACKLIST:
- yield s, e
- def get_reachable_graph(root: object) -> tuple[dict[int, object], dict[int, tuple[int, object]]]:
- parents = {}
- seen = {id(root): root}
- worklist = [root]
- while worklist:
- o = worklist.pop()
- for s, e in get_edges(o):
- if id(e) in seen:
- continue
- parents[id(e)] = (id(o), s)
- seen[id(e)] = e
- worklist.append(e)
- return seen, parents
- def get_path(
- o: object, seen: dict[int, object], parents: dict[int, tuple[int, object]]
- ) -> list[tuple[object, object]]:
- path = []
- while id(o) in parents:
- pid, attr = parents[id(o)]
- o = seen[pid]
- path.append((attr, o))
- path.reverse()
- return path
|