gpt4 book ai didi

python - 是否可以防止从卡住的 python 数据类中读取?

转载 作者:行者123 更新时间:2023-12-03 19:09:20 25 4
gpt4 key购买 nike

我有一种情况,我希望能够将卡住的 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 不是实例本身的函数。

最佳答案

我同意 Jon 的观点,即保留适当的数据 list 并更新共享实例将是解决问题的更好方法,但如果由于某种原因这不可能或不可行(您应该认真检查它是否是真的很重要),有一种方法可以实现您所描述的(顺便说一下,好的模型)。不过,它需要一些不平凡的代码,之后对您的数据类有一些限制:

from dataclasses import dataclass, replace, field
from typing import Any, ClassVar


@dataclass(frozen=True)
class AlwaysFreshData:
#: sentinel that is used to mark stale instances
STALE: ClassVar = object()

fresh_data: str
#: private staleness indicator for this instance
_freshness: Any = field(default=None, repr=False)

def __post_init__(self):
"""Updates a donor instance to be stale now."""

if self._freshness is None:
# is a fresh instance
pass
elif self._freshness is self.STALE:
# this case probably leads to inconsistent data, maybe raise an error?
print(f'Warning: Building new {type(self)} instance from stale data - '
f'is that really what you want?')
elif isinstance(self._freshnes, type(self)):
# is a fresh instance from an older, now stale instance
object.__setattr__(self._freshness, '_instance_freshness', self.STALE)
else:
raise ValueError("Don't mess with private attributes!")
object.__setattr__(self, '_instance_freshness', self)

def __getattribute__(self, name):
if object.__getattribute__(self, '_instance_freshness') is self.STALE:
raise RuntimeError('Instance went stale!')
return object.__getattribute__(self, name)
对于您的测试代码,其行为将如下所示:
# basic functionality
>>> original = AlwaysFreshData(fresh_data="fresh")
>>> original.fresh_data
fresh
>>> new = replace(original, fresh_data="even fresher")
>>> new.fresh_data
even_fresher

# if fresher data was used, the old instance is "disabled"
>>> original.fresh_data
Traceback (most recent call last):
File [...] in __getattribute__
raise RuntimeError('Instance went stale!')
RuntimeError: Instance went stale!

# defining a new, unrelated instance doesn't mess with existing ones
>>> runner_up = AlwaysFreshData(fresh_data="different freshness")
>>> runner_up.fresh_data
different freshness
>>> new.fresh_data # still fresh
even_fresher
>>> original.fresh_data # still stale
Traceback (most recent call last):
File [...] in __getattribute__
raise RuntimeError('Instance went stale!')
RuntimeError: Instance went stale!

需要注意的一件重要事情是,这种方法为数据类引入了一个新字段,即 _freshness ,它可能会手动设置并弄乱整个逻辑。您可以尝试在 __post_init__ 中捕获它,但这样的事情将是让旧实例保持新鲜的有效偷偷摸摸的方式:
>>> original = AlwaysFreshData(fresh_data="fresh")
# calling replace with _freshness=None is a no-no, but we can't prohibit it
>>> new = replace(original, fresh_data="even fresher", _freshness=None)
>>> original.fresh_data
fresh
>>> new.fresh_data
even_fresher
此外,我们需要一个默认值,这意味着它下面声明的任何字段也需要一个默认值(这还不错 - 只需声明它上面的那些字段),包括来自 future 子项的所有字段(这更多的是一个问题,并且有一个关于如何处理这种情况的 huge post)。
每当您使用这种模式时,您还需要一个可用的哨兵值。这并不是很糟糕,但对某些人来说可能是一个奇怪的概念。

关于python - 是否可以防止从卡住的 python 数据类中读取?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62678273/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com