refactor: ленивый резолв приоритетов без замены в репозитории

Изменения:
- _resolve_relative_priorities() возвращает словарь вместо замены
- Приоритеты не заменяются в ConversionPoint
- resolved_priorities передаётся в filter_exploded_callgraph_branch
- get_aggregate_priority использует resolved_priorities если есть

Преимущества:
- Относительные приоритеты сохраняются в репозитории
- Можно добавлять новые инжекторы после get_conversion()
- Нет мутации состояния репозитория
- Каждый вызов get_conversion() использует актуальные приоритеты

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
Qwen Code Assistant
2026-03-28 14:37:50 +00:00
parent 4c1568fd47
commit a71e9fd424
3 changed files with 44 additions and 34 deletions

View File

@@ -52,9 +52,9 @@ class ConvRepo:
allow_sync: bool = True, allow_sync: bool = True,
force_async: bool = False force_async: bool = False
): ):
# Разрешаем относительные приоритеты # Разрешаем относительные приоритеты (не заменяя, а получая словарь)
self._resolve_relative_priorities() resolved_priorities = self._resolve_relative_priorities()
filtered_injectors = self.filtered_injectors(allow_async, allow_sync) filtered_injectors = self.filtered_injectors(allow_async, allow_sync)
pipeline_callseq = [] pipeline_callseq = []
orig_from_types = tuple(from_types) orig_from_types = tuple(from_types)
@@ -71,7 +71,13 @@ class ConvRepo:
else: else:
injects = extract_return_type(fn) injects = extract_return_type(fn)
callseq = self.get_callseq(filtered_injectors, frozenset(from_types), fn, force_commutative) callseq = self.get_callseq(
filtered_injectors,
frozenset(from_types),
fn,
force_commutative,
resolved_priorities
)
pipeline_callseq += callseq pipeline_callseq += callseq
@@ -133,7 +139,8 @@ class ConvRepo:
injectors: frozenset[ConversionPoint], injectors: frozenset[ConversionPoint],
from_types: frozenset[type], from_types: frozenset[type],
fn: Callable | Iterable[ConversionPoint] | ConversionPoint, fn: Callable | Iterable[ConversionPoint] | ConversionPoint,
force_commutative: bool) -> list[ConversionPoint]: force_commutative: bool,
resolved_priorities: Optional[dict[ConversionPoint, float]] = None) -> list[ConversionPoint]:
cg = self.walker.generate_callgraph(injectors, from_types, fn) cg = self.walker.generate_callgraph(injectors, from_types, fn)
if cg is None: if cg is None:
@@ -142,12 +149,12 @@ class ConvRepo:
for inj in injectors: for inj in injectors:
available_types.add(inj.injects) available_types.add(inj.injects)
available_types.update(inj.requires) available_types.update(inj.requires)
# Определяем требуемые типы # Определяем требуемые типы
required_types = set() required_types = set()
if callable(fn): if callable(fn):
required_types = extract_func_argtypes(fn) required_types = extract_func_argtypes(fn)
raise NoConversionPath( raise NoConversionPath(
from_types=tuple(from_types), from_types=tuple(from_types),
target=fn, target=fn,
@@ -157,7 +164,11 @@ class ConvRepo:
exploded = self.walker.explode_callgraph_branches(cg, from_types) exploded = self.walker.explode_callgraph_branches(cg, from_types)
selected = self.walker.filter_exploded_callgraph_branch(exploded) # Передаём resolved_priorities в filter_exploded_callgraph_branch
selected = self.walker.filter_exploded_callgraph_branch(
exploded,
resolved_priorities=resolved_priorities
)
if len(selected) == 0: if len(selected) == 0:
raise NoConversionPath( raise NoConversionPath(
from_types=tuple(from_types), from_types=tuple(from_types),
@@ -222,8 +233,8 @@ class ConvRepo:
reason="force_async=True requires allow_async=True" reason="force_async=True requires allow_async=True"
) )
# Разрешаем относительные приоритеты # Разрешаем относительные приоритеты (не заменяя, а получая словарь)
self._resolve_relative_priorities() resolved_priorities = self._resolve_relative_priorities()
filtered_injectors = self.filtered_injectors(allow_async, allow_sync) filtered_injectors = self.filtered_injectors(allow_async, allow_sync)
@@ -231,7 +242,8 @@ class ConvRepo:
filtered_injectors, filtered_injectors,
frozenset(from_types), frozenset(from_types),
fn, fn,
force_commutative force_commutative,
resolved_priorities
) )
ret_fn = self.renderer.render(from_types, callseq, force_async=force_async, store_sources=self.store_sources) ret_fn = self.renderer.render(from_types, callseq, force_async=force_async, store_sources=self.store_sources)
@@ -253,30 +265,22 @@ class ConvRepo:
""" """
Разрешить относительные приоритеты и вычислить абсолютные значения. Разрешить относительные приоритеты и вычислить абсолютные значения.
Проходит по всем инжекторам и если есть относительные приоритеты, Не заменяет приоритеты в репозитории, а возвращает словарь
вычисляет абсолютные значения на основе графа зависимостей. {ConversionPoint: float_priority} для использования в graph_walker.
Returns:
Dict[ConversionPoint, float] или None если нет относительных приоритетов
""" """
injectors = list(self.convertor_set) injectors = list(self.convertor_set)
# Проверяем есть ли относительные приоритеты # Проверяем есть ли относительные приоритеты
has_relative = any(isinstance(cp.priority, RelativePriority) for cp in injectors) has_relative = any(isinstance(cp.priority, RelativePriority) for cp in injectors)
if not has_relative: if not has_relative:
return return None
try: try:
priorities = resolve_priorities(injectors) priorities = resolve_priorities(injectors)
return priorities
# Применяем разрешённые приоритеты
# Создаём новые ConversionPoint с абсолютными приоритетами
new_injectors = set()
for cp in injectors:
if cp in priorities:
new_injectors.add(cp.copy_with(priority=priorities[cp]))
else:
new_injectors.add(cp)
# Обновляем репозиторий
self._convertor_set = new_injectors
except CycleDetectedError as e: except CycleDetectedError as e:
# Переупаковываем в наше исключение # Переупаковываем в наше исключение

View File

@@ -134,7 +134,8 @@ class GraphWalker:
def filter_exploded_callgraph_branch(cls, def filter_exploded_callgraph_branch(cls,
variants: list[CallgraphVariant], variants: list[CallgraphVariant],
priority_injectors: Optional[frozenset[ConversionPoint | Callable]] = None, priority_injectors: Optional[frozenset[ConversionPoint | Callable]] = None,
relevance_metric: Optional[Callable[[CallgraphVariant], int | float]] = None) \ relevance_metric: Optional[Callable[[CallgraphVariant], int | float]] = None,
resolved_priorities: Optional[dict[ConversionPoint, float]] = None) \
-> list[CallgraphVariant]: -> list[CallgraphVariant]:
if relevance_metric is None: if relevance_metric is None:
@@ -148,7 +149,7 @@ class GraphWalker:
for metric in template_metrics: for metric in template_metrics:
if len(variants) == 1: if len(variants) == 1:
break break
new_variants = cls.filter_exploded_callgraph_branch(variants, priority_injectors, metric) new_variants = cls.filter_exploded_callgraph_branch(variants, priority_injectors, metric, resolved_priorities)
if len(new_variants) > 0: if len(new_variants) > 0:
variants = new_variants variants = new_variants
@@ -156,21 +157,26 @@ class GraphWalker:
if len(variants) > 1: if len(variants) > 1:
# Вычисляем aggregate priority для каждого варианта (сумма приоритетов всех инжекторов в пути) # Вычисляем aggregate priority для каждого варианта (сумма приоритетов всех инжекторов в пути)
def get_aggregate_priority(variant: CallgraphVariant) -> float: def get_aggregate_priority(variant: CallgraphVariant) -> float:
priority = variant.injector.priority # Используем resolved_priorities если есть, иначе берём из cp.priority
if resolved_priorities and variant.injector in resolved_priorities:
priority = resolved_priorities[variant.injector]
else:
priority = variant.injector.priority if isinstance(variant.injector.priority, (int, float)) else 0.0
for subg in variant.subgraphs: for subg in variant.subgraphs:
for subv in subg.variants: for subv in subg.variants:
priority += get_aggregate_priority(subv) priority += get_aggregate_priority(subv)
return priority return priority
# Сортировка по aggregate priority (обратный порядок - выше приоритет = раньше) # Сортировка по aggregate priority (обратный порядок - выше приоритет = раньше)
# Затем по имени функции для детерминизма # Затем по имени функции для детерминизма
variants.sort(key=lambda x: (-get_aggregate_priority(x), universal_qualname(x.injector.fn))) variants.sort(key=lambda x: (-get_aggregate_priority(x), universal_qualname(x.injector.fn)))
# Выбираем вариант с наивысшим aggregate приоритетом # Выбираем вариант с наивысшим aggregate приоритетом
max_priority = get_aggregate_priority(variants[0]) max_priority = get_aggregate_priority(variants[0])
selected = [v for v in variants if get_aggregate_priority(v) == max_priority] selected = [v for v in variants if get_aggregate_priority(v) == max_priority]
variants = selected variants = selected
return variants return variants
if len(variants) < 2: if len(variants) < 2:

4
uv.lock generated
View File

@@ -1,10 +1,10 @@
version = 1 version = 1
revision = 2 revision = 3
requires-python = ">=3.13" requires-python = ">=3.13"
[[package]] [[package]]
name = "breakshaft" name = "breakshaft"
version = "0.1.0.post2" version = "0.1.6.post5"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "hatchling" }, { name = "hatchling" },