У меня есть ситуация, когда я хотел бы иметь возможность обрабатывать замороженный экземпляр dataclass
как всегда имеющий самые свежие данные. Или, другими словами, я хотел бы иметь возможность определять, вызывается ли экземпляр класса данных replace
, и генерировать исключение. Это также должно применяться только к этому конкретному экземпляру, чтобы создание / замена других экземпляров класса данных того же типа не влияли друг на друга.
Вот пример кода:
from dataclasses import dataclass, replace
@dataclass(frozen=True)
class AlwaysFreshData:
fresh_data: str
def attempt_to_read_stale_data():
original = AlwaysFreshData(fresh_data="fresh")
unaffected = AlwaysFreshData(fresh_data="not affected")
print(original.fresh_data)
new = replace(original, fresh_data="even fresher")
print(original.fresh_data) # I want this to trigger an exception now
print(new.fresh_data)
Идея состоит в том, чтобы предотвратить как случайную мутацию, так и чтение устаревших данных из объектов нашего класса данных, чтобы предотвратить ошибки.
Можно ли это сделать? Либо через базовый класс, либо каким-то другим методом?
РЕДАКТИРОВАТЬ: намерение здесь состоит в том, чтобы иметь способ принудительно / проверять семантику владения для классов данных, даже если это только во время выполнения.
Вот конкретный пример проблемной ситуации с обычными классами данных.
@dataclass
class MutableData:
my_string: str
def sneaky_modify_data(data: MutableData) -> None:
some_side_effect(data)
data.my_string = "something else" # Sneaky string modification
x = MutableData(my_string="hello")
sneaky_modify_data(x)
assert x.my_string == "hello" # as a caller of 'sneaky_modify_data', I don't expect that x.my_string would have changed!
Этого можно избежать, используя замороженные классы данных! Но все же существует ситуация, которая может привести к потенциальным ошибкам, как показано ниже.
@dataclass(frozen=True)
class FrozenData:
my_string: str
def modify_frozen_data(data: FrozenData) -> FrozenData:
some_side_effect(data)
return replace(data, my_string="something else")
x = FrozenData(my_string="hello")
y = modify_frozen_data(x)
some_other_function(x) # AHH! I probably wanted to use y here instead, since it was modified!
Таким образом, я хочу иметь возможность предотвращать скрытые или неизвестные модификации данных, а также принудительно отменять данные, которые были заменены. Это предотвращает возможность случайного использования устаревших данных.
Некоторым эта ситуация может быть знакома как подобная семантике владения в чем-то вроде Rust.
Что касается моей конкретной ситуации, у меня уже есть большой объем кода, который использует эту семантику, за исключением экземпляров NamedTuple
. Это работает, потому что изменение функции _replace
в любом экземпляре позволяет сделать экземпляры недействительными. Эта же стратегия не работает так чисто для классов данных, поскольку dataclasses.replace
не является функцией самих экземпляров.
fresh_data
. - person jonrsharpe   schedule 01.07.2020replace
), но хотите, чтобы данные были заморожены, поэтому вы создаете новый экземпляр. В то же время вы не хотите, чтобы использовался старый экземпляр, потому что он больше не действителен. Почему бы просто не сделать объект незамороженным, как предложил @jonrsharpe? Тогда у вас не будет никаких проблем, которые вы сейчас пытаетесь решить. Объясните вам исходную проблему X, вместо того, чтобы просить решения для Y. - person zvone   schedule 03.07.2020replace
- вызываться с замороженными классами данных. Хорошо указать, что проблема намекает на проблему дизайна, но обстоятельства OP или любого, кто найдет этот пост через Google, могут сделать редизайн невозможным. - person Arne   schedule 03.07.2020replace
- сделать копию, и это нормально, но вопрос здесь в том, чтобы пометить исходный замороженный объект как использованный, и это всегда очень подозрительный дизайн. - person zvone   schedule 03.07.2020some_side_effect
, потому что не доверяете этому? Вы ничего не добьетесь, если не доверяете тем, что делают функции. Во-вторых, интерфейсmodify_frozen_data
не должен ограничиваться доверием какой-либо другой функции. Вы хотите изменить данные вmodify_frozen_data
, поэтому их можно изменить. Если вы действительно не можете доверятьsome_side_effect
(но подумайте об этом еще раз!), Тогда не присылайте ему свой объект - отправьте копию. - person zvone   schedule 03.07.2020copy.copy
иcopy.deepcopy
отлично работают с классами данных, а _ 3_ предназначен, в частности, для обработки замороженных классов данных, когда копирование + обновление не работает. - person Arne   schedule 04.07.2020some_side_effect
ничего не меняет, это просто способ представить некую работу, которая не меняет данные. Идея использования замороженного класса данных состоит в том, чтобы предотвратить модификацию без явного указания. Однако это по-прежнему не предотвращает использование неизмененных данных, что может привести к незаметным, трудно отслеживаемым ошибкам. Очевидно, что с подобными вещами можно справиться, если проводить более тщательный и качественный анализ кода, но было бы неплохо иметь возможность выявлять случаи, когда это происходит, до того, как они вызовут ошибки в производственной среде. - person Sanchit Uttam   schedule 06.07.2020