Возвращайте класс, ничего не меняя

#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 является более простым решением.