Tipragot
628be439b8
Cela permet de ne pas avoir de problèmes de compatibilité car python est dans le git.
102 lines
3.2 KiB
Python
102 lines
3.2 KiB
Python
"""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
|