如何使用 tuple 和 dict 解包但没有额外的方法来制作 attrs class

how to make attrs class with tuple and dict unpacking but without extra methods

我刚开始使用 python 的 attrs 模块,它非常灵活(或者类似地我们可以使用 Python 3.7 DataClasses)。我的一个常见使用模式是 class 作为参数值的容器。我喜欢分配参数时的标签,以及更清晰的属性样式引用值,但我也喜欢有一些功能,这些功能在将值存储在有序字典中时非常有用:

  1. *tuplelist 一样解包以输入函数参数
  2. ** 在需要或需要关键字传递时解包。

我可以通过向 class

添加三个方法来实现所有这些
@attr.s
class DataParameters:
    A: float = attr.ib()
    alpha: float = attr.ib()
    c: float = attr.ib()
    k: float = attr.ib()
    M_s: float = attr.ib()

    def keys(self):
        return 'A', 'alpha', 'c', 'k', 'M_s'

    def __getitem__(self, key):
        return getattr(self, key)

    def __iter__(self):
        return (getattr(self, x) for x in self.keys())

然后我可以像这样使用 classes:

params = DataParameters(1, 2, 3, 4, 5)
result1 = function1(100, 200, *params, 300)
result2 = function2(x=1, y=2, **params)

这里的动机是数据classes 提供了方便和清晰。但是,有一些原因导致我不要求我正在编写的模块使用数据 class。希望函数调用应该接受简单的参数,而不是复杂的数据classes.

上面的代码很好,但我想知道我是否遗漏了一些可以让我完全跳过编写函数的东西,因为模式非常清晰。属性按照我希望它们解包的顺序添加,并且可以根据关键字参数的属性名称读取为键值对。

也许这是这样的:

@addtupleanddictunpacking
@attr.s
class DataParameters:
    A: float = attr.ib()
    alpha: float = attr.ib()
    c: float = attr.ib()
    k: float = attr.ib()
    M_s: float = attr.ib()

但我不确定 attrs 本身是否有我没有找到的东西可以做到这一点。此外,我不确定如何在添加属性时保持属性的顺序并将其转换为键方法。

它没有直接集成到 class 中,但 the asdict and astuple 辅助函数旨在执行此类转换。

params = DataParameters(1, 2, 3, 4, 5)
result1 = function1(100, 200, *attr.astuple(params), 300)
result2 = function2(x=1, y=2, **attr.asdict(params))

它们没有集成到 class 本身,因为这会使 class 表现为序列或映射 无处不在 ,这可能会导致无声预期 TypeError/AttributeError 时的不当行为。在性能方面,这应该没问题;无论如何,解包都会转换为 tuple/dict(它不能直接传递不是 tupledict 的内容,因为 C API 期望能够在其参数上使用特定于类型的 API)。

如果你真的想让 class 充当序列或映射,你基本上必须做你已经做过的事情,尽管你可以使用辅助函数来减少自定义代码和重复的变量名,例如:

@classmethod
def keys(cls):
    return attr.fields_dict(cls).keys()

def __getitem__(self, key):
    return getattr(self, key)

def __iter__(self):
    return iter(attr.astuple(self, recurse=False))  

扩展@ShadowRanger 的想法,可以制作您自己的装饰器,其中包含 attr.s 和 attr.ib 以获得更简洁的解决方案,基本上添加额外的处理。

import attr
field = attr.ib  # alias because I like it

def parameterset(cls):
    cls = attr.s(cls)

    # we can use a local variable to store the keys in a tuple
    # for a faster keys() method
    _keys = tuple(attr.fields_dict(cls).keys())
    @classmethod
    def keys(cls):
    #     return attr.fields_dict(cls).keys()
        return (key for key in _keys)

    def __getitem__(self, key):
        return getattr(self, key)

    def __iter__(self):
        return iter(attr.astuple(self, recurse=False))

    cls.keys = keys
    cls.__getitem__ = __getitem__
    cls.__iter__ = __iter__

    return cls

@parameterset
class DataParam:
    a: float = field()
    b: float = field()

dat = DataParam(a=1, b=2)
print(dat)
print(tuple(dat))
print(dict(**dat))

给出输出

DataParam(a=1, b=2)
(1, 2)
{'a': 1, 'b': 2}