#python
Вопрос:
Я хочу, чтобы следующая функция возвращала новое состояние, которое обновляется с помощью измененной / новой переменной. Состояние test_next_state является тестом, следовательно, исправлено.
def test_next_state_immutable(): s1 = State(v1 = False, v2 = True, v3 = "open") s2 = State(v1 = True, v2 = True, v3 = "open") res = s1.next(v1 = True) assert s1 != s2
Это мое решение, но моя проблема в том, что при создании TempState он указывает на сам объект и изменяет исходное значение, чего я не хочу. Я хочу вернуть новое состояние, ничего не меняя в старом состоянии.
from __future__ import annotations from typing import Any, Optional from dataclasses import dataclass @dataclass(frozen=True, order=True, init=False) class State: state: dict[str, Any] def __init__(self, **kwargs) -gt; None: object.__setattr__(self, "state", kwargs) def next(self, **kwargs) -gt; State: tempState = dataclasses.replace(self) for k, v in kwargs.items(): tempState.state[k] = v return tempState def __hash__(self) -gt; int: return hash(tuple(sorted(self.state)))
Ответ №1:
Просто создайте новый экземпляр State
, используя dict
созданный из текущего состояния и любые обновления с помощью |
оператора.
@dataclass(frozen=True, order=True, init=False) class State: state: dict[str, Any] def __init__(self, **kwargs): self.state = dict(kwargs) def next(self, **kwargs): return State(**(self.state | kwargs))
До Python 3.9 вам нужно будет использовать collections.ChainMap
вместо |
оператора.
import collections ... def next(self, **kwargs): return State(**collections.ChainMap(kwargs, self.state))
Комментарии:
1.
ChainMap
Решение должно поменять порядок, чтобыkwargs
он был раньшеself.state
;ChainMap
приоритет значения отдается первому переданному отображению, а не последнему, поэтому передачаself.state
первого означает, что вместо новых значений используются существующие значенияkwargs
.2. Я даже просмотрел документацию (казалось бы, плохо), чтобы проверить, какой заказ был необходим 🙁
3. Вместо цепной карты вы могли бы просто использовать
{**self.state, **kwargs}
, хотя я бы использовал переменную, прежде чем вводить ее в конструктор4. Я упомянул
ChainMap
об этом главным образом потому, что всегда забываю о его существовании. Теоретически это более эффективно, так как ему не нужно создавать новыйdict
; он просто хранит существующие правила для последующего повторения, что в данном случае происходит немедленно. (Преимущество, которое у него|
тоже есть.)
Ответ №2:
dataclasses.replace
без аргументов эквивалентно неглубокому копированию экземпляра, что означает, что оба экземпляра имеют общий псевдоним, совпадающий dict
с их .state
атрибутом. Вы даже ничего не используете replace
для изменения, поэтому минимальное исправление-добавить import copy
в начало файла и изменить:
tempState = dataclasses.replace(self)
Для
tempState = copy.deepcopy(self)
что приведет к нарушению сглаживания между tempState.state
и self.state
путем рекурсивного копирования, чтобы получить глубокую копию.
В качестве альтернативы, в этом случае, когда создание нового dict
довольно просто , вы можете просто создать новое dict
заранее и использовать его replace
, например:
def next(self, **kwargs) -gt; State: return dataclasses.replace(self, state=self.state | kwargs) # dict union added in 3.9 # 3.7-3.8 version without dict union operator: def next(self, **kwargs) -gt; State: temp_state = self.state.copy() temp_state.update(kwargs) return dataclasses.replace(self, state=temp_state)
Это предполагает возможность того, что существуют и другие поля, кроме state
того replace
, что полезно для их сохранения; если нет, то решение Чепнера о непосредственном создании нового State
, а не использовании replace
его в качестве основы, self
является более простым решением.