如何识别属性的属性何时被设置? - 尽管属性是一个对象列表

How to identify when an attribute's attribute is being set? - Although attribute is a list of objects

我看过这个堆栈溢出问题:。我也想这样做,但是如果可变属性是对象列表而不仅仅是一个对象怎么办?该解决方案仍然有效吗?问题是,当有一个列表时,有时我想在列表的一个对象已更改或列表中的所有对象都已更改时进行一些计算 - 问题是如果所有对象都已更改,我 不要每次更改列表项时都不想重复计算,因为计算是相同的,但只有当列表的所有值都更改时才有效.

例如,

我不想要这个:

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()

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

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

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 class 的同一示例的扩展版本。请注意,为了进行比较,我们需要在 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 或所有这些方法都已经实现的类似容器会更容易。