【问题标题】:How to identify when an attribute's attribute is being set? - Although attribute is a list of objects如何识别何时设置属性的属性? - 虽然属性是对象列表
【发布时间】:2021-10-02 15:25:33
【问题描述】:

我见过这个堆栈溢出问题:link。我也想做同样的事情,但是如果可变属性是一个对象列表而不是一个对象呢?无论如何,该解决方案会起作用吗?问题是当有一个列表时,有时我想在列表的一个对象已更改或列表中的所有对象已更改时进行一些计算 - 问题是如果所有对象都已更改,我 不想希望每次更改列表中的项目时都重复计算,因为计算将是相同的,但只有当列表的所有值都更改时才有效。 p>

例如,

我不想要这个:

Set element[0] -> computation -> set element[1] -> computation ... -> set element[n-1] -> computation

我想要这个:

Set element[0] -> set element[1] -> set element[2] -> ... -> set element[n-1] -> computation

可以这样做吗?同时,我将尝试使用附加问题的代码,如果我有任何更新,我会通知您。

class Foomutable(object):
    def __init__(self):
        self.attr0 = 0
        self.attr1 = 1

class Foo(object):
    def __init__(self):
        self.mutable = [Foomutable()]*4
        
    def computation(self):
        sum = 0.0
        for mut in self.mutable:
            sum += mut.attr0 * mut.attr1
        return sum

    
    @property
    def mutable(self):
        return self._mutable

    @mutable.setter # Set attributes of mutable
    def mutable(self, attrs):
        # mutable is the list of objects
        # Here if one or all the objects in mutable change the value of their attributes then
        # do computation.
        self.computation()

目前改变 mutable 的方式是循环每个元素改变它的一个或多个属性。

【问题讨论】:

  • 这感觉就像您希望 element 成为上下文管理器,其中项目在上下文打开时是可变的,而计算在上下文关闭时发生。 (这基本上是文件句柄的工作方式——您可以在它们打开时将大量单独的数据写入其中,并且将数据刷新到磁盘的最终“计算”发生在关闭时,以避免一堆小写。)
  • 是的@Samwise,我也是这么想的。我认为有一面旗帜是值得的。但我不知道这是否会在可变类或监视器可变类中。想法是,当标志为真(或可变类中的所有标志???)时,父级进行计算,计算后标志应再次设置为假
  • 我不确定“监控可变类”是什么——如果您包含一个实际的类定义并指出您希望它如何工作,它可能会有所帮助!
  • 添加了简短的python代码
  • 您能否举例说明您希望如何使用mutable 属性?例如。您是在按照您的代码所暗示的那样做Foo.mutable = [1, 2, 3, 4],还是按照您的问题描述所暗示的那样尝试做Foo.mutable[0] = 1Foo.mutable[1] = 2 等?对于后者,我认为mutable 需要通过__setitem__ 返回实现您正在寻找的语义的 another 对象。

标签: python properties attributes


【解决方案1】:

我认为解决此问题的最佳方法是拥有一个上下文管理器,该管理器授予对可变对象的访问权限,并在上下文退出时进行计算。这是一个简单的例子:

from contextlib import contextmanager
from typing import ContextManager, Iterator, List


class Foo:
    def __init__(self) -> None:
        self._mutable = [0, 1, 2, 3]
        self._total = sum(self._mutable)

    def computation(self) -> None:
        self._total = sum(self._mutable)
        print(f"* new computed total: {self._total}")

    @property
    def mutable(self) -> ContextManager[List[int]]:
        @contextmanager
        def context() -> Iterator[List[int]]:
            old = self._mutable.copy()
            try:
                yield self._mutable
            finally:
                if old != self._mutable:
                    print(f"* changed {old} to {self._mutable}, recomputing!")
                    self.computation()
        return context()


foo = Foo()
with foo.mutable as m:
    print(f"Current attributes: {m}")
    # no computation happens because we didn't change anything
with foo.mutable as m:
    print(f"Now we're going to change them...")
    m[0] = 10
    m[1] = 10
    m[2] = 10
    m[3] = 10
    # recomputation happens here!
with foo.mutable as m:
    print(f"All done!  {m}")

输出:

Current attributes: [0, 1, 2, 3]
Now we're going to change them...
* changed [0, 1, 2, 3] to [10, 10, 10, 10], recomputing!
* new computed total: 40
All done!  [10, 10, 10, 10]

在本例中,我只是使用List[int] 作为可变对象;您可以将相同的通用技术应用于任何其他可变对象,包括可变对象列表,只要您可以在上下文管理器中正确实现比较,以确定是否有任何更改需要重新计算。

这是使用 FooMutable 类的同一示例的扩展版本。请注意,为了进行比较,我们需要在 FooMutable 对象上实现 __eq__,并且我们还需要确保我们正在深度复制两个列表(否则“旧”列表只是引用相同的可变对象)。

from contextlib import contextmanager
from typing import ContextManager, Iterator, List


class FooMutable:
    def __init__(self) -> None:
        self.attr0 = 0
        self.attr1 = 1

    def __eq__(self, other: object) -> bool:
        return (
            isinstance(other, FooMutable)
            and self.__dict__ == other.__dict__
        )

    def __repr__(self) -> str:
        return f"<{'+'.join(map(str, self.__dict__.values()))}>"

    def copy(self) -> 'FooMutable':
        other = FooMutable()
        other.__dict__.update(self.__dict__)
        return other


class Foo:
    def __init__(self) -> None:
        self._mutable = [FooMutable() for _ in range(4)]
        self._total = 4

    def computation(self) -> None:
        self._total = sum(sum(m.__dict__.values()) for m in self._mutable)
        print(f"* new computed total: {self._total}")

    @property
    def mutable(self) -> ContextManager[List[FooMutable]]:
        @contextmanager
        def context() -> Iterator[List[FooMutable]]:
            old = [f.copy() for f in self._mutable]
            try:
                yield self._mutable
            finally:
                if old != self._mutable:
                    print(f"* changed {old} to {self._mutable}, recomputing!")
                    self.computation()
        return context()


foo = Foo()
with foo.mutable as m:
    print(f"Current attributes: {m}")
    # no computation happens because we didn't change anything
with foo.mutable as m:
    print(f"Now we're going to change them...")
    m[0].attr1 = 10
    m[1].attr1 = 10
    m[2].attr1 = 10
    m[3].attr1 = 10
    # recomputation happens here!
with foo.mutable as m:
    print(f"All done!  {m}")

请注意,我对FooMutable 的实现主要是传递给它的__dict__;如果它只是充当可变容器,则将其设置为 TypedDict 或已实现所有这些方法的类似容器会更容易。

【讨论】:

  • 有趣...我不知道上下文管理器。但是,正如您所说,您的示例仅在将项目更改为相同类型时才有效。换句话说,如果我使用对象,我需要通过一个对象更改每个项目,但如果我更改对象的属性,则将无法正常工作。
  • 您可以将相同的通用技术应用于任何其他可变对象,包括可变对象列表,只要您可以在上下文管理器中正确实现比较,以确定是否有任何更改这需要重新计算。 您所要做的就是确保进行深度比较——对于Foomutables 的列表,我认为只需要在Foomutable 上实现__eq__。跨度>
  • 另一个问题可能与主题相距甚远...为什么访问或打印属性和变量的唯一方法是使用 foo.mutable 作为 m?我收到 TypeError: '_GeneratorContextManager' object is not subscriptable 如果我不这样做
  • 因为它是一个上下文管理器。您必须使用 with 语法才能调用 context() 函数(在 mutable 属性内),该函数将开始跟踪您是否已更新可变对象; with 块决定了它何时检查最后的更改。如果您能够在 with 块之外改变对象,您的 computation 将不会运行。
  • 我认为__eq__ 需要在FoomutableFoo 类上实现,对吧?
猜你喜欢
  • 2017-05-10
  • 1970-01-01
  • 2013-07-17
  • 2018-11-20
  • 1970-01-01
  • 2020-09-13
  • 2017-06-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多