使用“attrs”包将传递的数据存储在对象中两次

Storing passed data in object twice with `attrs` package

我正在创建一个数据提供程序 class,它将保存数据、执行转换并使其可供其他 classes 使用。

如果用户创建此 class 的实例并在实例化时传递一些数据,我想将其存储两次:一次用于所有转换,一次作为原始数据的副本。假设数据本身有一个 copy 方法。

我正在使用 attrs 包来创建 classes,但也对通常的最佳方法感兴趣(也许有更好的方法来获得我想要的东西? )

这是我目前的情况:

@attr.s
class DataContainer(object):
    """Interface for managing data. Reads and write data, acts as a provider to other classes.
    """

    data = attr.ib(default=attr.Factory(list))
    data_copy = data.copy()

    def my_func(self, param1='all'):
        """Do something useful"""
        return param1

这不起作用:AttributeError: '_CountingAttr' object has no attribute 'copy'

我也无法调用 data_copy = self.data.copy(),我收到错误消息:NameError: name 'self' is not defined

没有 attrs 包的等效工作是:

class DataContainer(object):
    """Interface for managing data. Reads and write data, acts as a provider to other classes.
    """
    def __init__(self, data):
        "Init method, saving passed data and a backup copy"
        self.data = data
        self.data_copy = data

编辑:

正如@hynek 所指出的,我上面的简单 init 方法需要更正以制作数据的实际副本:即 self.data_copy = data.copy()。否则 self.dataself.data_copy 将指向同一个对象。

在查看 the documentation a little more deeply(向右滚动到底部)后,我发现有一种 post-init 钩子 类 是由 attrs 创建的.

您可以只包含一个特殊的 __attrs_post_init__ 方法,它可以在 __init__ 方法中完成人们可能想做的更复杂的事情,而不仅仅是简单的赋值。

这是我的最终工作代码:

In [1]: @attr.s
     ...: class DataContainer(object):
     ...:    """Interface for managing data. Reads and write data,
     ...:    acts as a provider to other classes.
     ...:    """
     ...: 
     ...:    data = attr.ib()
     ...: 
     ...:    def __attrs_post_init__(self):
     ...:        """Perform additional init work on instantiation.
     ...:        Make a copy of the raw input data.
     ...:        """
     ...:        self.data_copy = self.data.copy()



In [2]: some_data = np.array([[1, 2, 3], [4, 5, 6]])

In [3]: foo = DataContainer(some_data)

In [4]: foo.data
Out[5]: 
array([[1, 2, 3],
       [4, 5, 6]])

In [6]: foo.data_copy
Out[7]: 
array([[1, 2, 3],
       [4, 5, 6]])

为了加倍确定,我检查了这两个属性是否引用了同一个对象。在这种情况下,它们不是,这可能要归功于 NumPy 数组上的 copy 方法。

In [8]: foo.data[0,0] = 999

In [9]: foo.data
Out[10]: 
array([[999,   2,   3],
       [  4,   5,   6]])

In [11]: foo.data_copy
Out[12]: 
array([[1, 2, 3],
       [4, 5, 6]])

你可以在这里做两件事。

您自己找到的第一个:您使用 __attr_post_init__

第二个是默认值:

>>> import attr
>>> @attr.s
... class C:
...     x = attr.ib()
...     _x_backup = attr.ib()
...     @_x_backup.default
...     def _copy_x(self):
...         return self.x.copy()
>>> l = [1, 2, 3]
>>> i = C(l)
>>> i
C(x=[1, 2, 3], _x_backup=[1, 2, 3])
>>> i.x.append(4)
>>> i
C(x=[1, 2, 3, 4], _x_backup=[1, 2, 3])

JFTR,你是

的榜样
def __init__(self, data):
    self.data = data
    self.data_copy = data

是错误的,因为您将同一个对象赋值两次,这意味着修改 self.data 也会修改 self.data_copy,反之亦然。