使用`attrs`包

时间:2018-03-14 21:17:46

标签: python class python-attrs

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

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

我正在使用attrs包来创建类,但是也会对这方面的最佳方法感兴趣(也许有更好的方法来获取我所追求的内容?)

这是我到目前为止所做的:

@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所指出的,我需要更正上面的简单初始化方法来制作数据的实际副本:即self.data_copy = data.copy()。否则,self.dataself.data_copy都会指向同一个对象。

2 个答案:

答案 0 :(得分:1)

你可以在这做两件事。

您自己发现的第一个:您使用__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,反之亦然。

答案 1 :(得分:0)

在查看the documentation a little more deeply(向右滚动到底部)之后,我发现有一种由__attrs_post_init__创建的类的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]]) 方法中可能要执行的更复杂的操作。

这是我最后的工作代码:

copy

为了加倍确定,我检查了两个属性是否引用了同一个对象。在这种情况下它们不是,这可能归功于NumPy数组上的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]]) 方法。

--Number the rows
;with cte AS (
SELECT ROW_NUMBER() OVER(PARTITION BY companyname, city ORDER BY Address) AS counter_
, *
FROM #company 

--convert the number 1's to blanks and subtract each number by 1 so the 2nd record has a 1 appended
), cte2 AS (

SELECT *, 
CASE WHEN CAST(counter_ AS varchar(3)) = 1 THEN '' ELSE CAST(CAST(counter_ AS INT) - 1 AS VARCHAR(3)) END AS numberformatting 
FROM CTE
)
--if the formatted number is a blank, dont append anything. If it is non-blank, append a hyphen and the number
SELECT DISTINCT CASE WHEN numberformatting = '' THEN companyname + '(' + city + ')' ELSE companyname + '(' + city + '-' + numberformatting + ')' END AS formattedName, 
city, 
companyname, 
city, 
address
FROM cte2