diff --git a/src/breakshaft/graph_walker.py b/src/breakshaft/graph_walker.py index a3625ac..9e8c627 100644 --- a/src/breakshaft/graph_walker.py +++ b/src/breakshaft/graph_walker.py @@ -121,37 +121,49 @@ class GraphWalker: if cache_key in cls._explode_cache: return cls._explode_cache[cache_key] - # Вычисляем - variants = [] + # Вычисляем лениво через generator + variants = list(cls._explode_callgraph_branches_lazy(g, from_types)) + + # Сохраняем в кэш + cls._explode_cache[cache_key] = variants + + return variants + + @classmethod + def _explode_callgraph_branches_lazy(cls, g: Callgraph, from_types: frozenset[type]): + """ + Ленивая версия explode_callgraph_branches (generator). + + Yields: + CallgraphVariant: Варианты преобразований по одному + """ for variant in g.variants: if len(variant.subgraphs) == 0: - variants.append(variant) + yield variant continue - subg_combinations: list[list[CallgraphVariant | None]] = [] + + # Собираем ленивые итераторы для подграфов + subg_iterators = [] for subg in variant.subgraphs: - combinations: list[CallgraphVariant] = cls.explode_callgraph_branches(subg, from_types) + combinations = list(cls._explode_callgraph_branches_lazy(subg, from_types)) if len(combinations) == 0: - subg_combinations.append([None]) + subg_iterators.append([None]) else: - subg_combinations.append(typing.cast(list[CallgraphVariant | None], combinations)) - - for combination in all_combinations(subg_combinations): + subg_iterators.append(combinations) + + # Ленивое декартово произведение + from .util import lazy_cartesian_product + for combination in lazy_cartesian_product(*subg_iterators): if None in combination: - combination.remove(None) + combination = [x for x in combination if x is not None] cons: frozenset[type] = frozenset() cum_cmb: frozenset[Callgraph] = frozenset() for cmb in combination: if cmb is not None: cons |= cmb.consumed_from_types cum_cmb |= {Callgraph(frozenset({cmb}))} - variants.append( - CallgraphVariant(variant.injector, cum_cmb, - variant.consumed_from_types | cons)) - - # Сохраняем в кэш - cls._explode_cache[cache_key] = variants - - return variants + yield CallgraphVariant(variant.injector, cum_cmb, + variant.consumed_from_types | cons) @classmethod def filter_exploded_callgraph_branch(cls, diff --git a/src/breakshaft/util.py b/src/breakshaft/util.py index b54f3f6..7705a89 100644 --- a/src/breakshaft/util.py +++ b/src/breakshaft/util.py @@ -97,6 +97,35 @@ def all_combinations(options: list[list[T]]) -> list[list[T]]: return [list(comb) for comb in product(*options)] +def lazy_cartesian_product(*iterables): + """ + Ленивое декартово произведение итераторов. + + В отличие от itertools.product, работает с итераторами + и генерирует результаты по одному. + + Args: + *iterables: Переменное число итераторов + + Yields: + list: Комбинация элементов (по одному элементу из каждого итератора) + + Пример: + >>> list(lazy_cartesian_product([1, 2], [3, 4])) + [[1, 3], [1, 4], [2, 3], [2, 4]] + """ + if not iterables: + yield [] + return + + first, *rest = iterables + first = iter(first) + + for item in first: + for combination in lazy_cartesian_product(*rest): + yield [item] + combination + + def get_tuple_types(type_obj: type) -> tuple: ret = ()