使用Python类作为数据容器

Using Python class as a data container

有时将相关数据聚集在一起是有意义的。我倾向于听写,例如,

1
2
self.group = dict(a=1, b=2, c=3)
print self.group['a']

我的一个同事喜欢创建一个类

1
2
3
4
5
6
7
class groupClass(object):
    def __init__(a, b, c):
        self.a = a
        self.b = b
        self.c = c
self.group = groupClass(1, 2, 3)
print self.group.a

注意,我们没有定义任何类方法。

我喜欢使用dict,因为我喜欢最小化代码行数。我的同事认为如果您使用一个类,代码的可读性会更高,而且将来向类中添加方法会更容易。

你喜欢哪一种?为什么?


在我看来,如果你真的从未定义任何类方法,那么dict或namedtuple就更有意义了。简单+内置很好!不过,对每个人都是如此。


背景

R.Hettinger在sf python的2017年假日会议上总结了基于属性的数据容器。看看他的推特和幻灯片。他还在2018年的Pycon大会上发表了关于数据类的演讲。

本文主要在python 3文档中提到了其他数据容器类型(请参见下面的链接)。

下面是关于将recordclass添加到标准库的python ideas邮件列表的讨论。

选项

标准库中的备选方案

  • collections.namedtuple:具有属性的元组(参见种子配方)
  • typing.NamedTuple子类tuple(见本帖与namedtuple比较)
  • types.SimpleNamespace:简单类w/可选类声明
  • types.MappingProxy:只读dict
  • enum.Enum:相关常量的约束集合(行为类似于类)
  • dataclasses.dataclass:具有默认/无样板类的可变名称

外部选项

  • 记录:可变名称复制(另请参见recordclass)
  • 束:增加对dicts的属性访问(SimpleNamedspace的灵感)
  • 框:带点样式查找功能的环绕式听写
  • attrdict:从映射中访问元素作为键或属性
  • 字段:从容器类中删除样板文件。
  • 名称列表:易变的、类似元组的容器,默认为E.Smith

哪一个?

决定使用哪个选项取决于具体情况(参见下面的示例)。通常一本老式的可变字典或不可变的名字就足够了。数据类是最新添加的(python 3.7a),它提供了可变性和可选的不可变性,并承诺在attrs项目的启发下减少样板文件。

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
import typing as typ
import collections as ct
import dataclasses as dc


# Problem: You want a simple container to hold personal data.
# Solution: Try a NamedTuple.
>>> class Person(typ.NamedTuple):
...     name: str
...     age: int
>>> a = Person("bob", 30)
>>> a
Person(name='bob', age=30)
1
2
3
4
5
6
7
8
9
10
# Problem: You need to change age each year, but namedtuples are immutable.
# Solution: Use assignable attributes of a traditional class.
>>> class Person:
...     def __init__(self, name, age):
...         self.name = name
...         self.age = age
>>> b = Person("bob", 30)
>>> b.age = 31
>>> b
<__main__.Person at 0x4e27128>
1
2
3
4
5
6
7
8
9
10
11
12
13
# Problem: You lost the pretty repr and want to add comparison features.
# Solution: Use included repr and eq features from the new dataclasses.
>>> @dc.dataclass(eq=True)
... class Person:
...     name: str
...     age: int
>>> c = Person("bob", 30)
>>> c.age = 31
>>> c
Person(name='bob', age=31)
>>> d = Person("dan", 31)
>>> c != d
True


我更喜欢跟着雅格尼用听写。


有一个新的提议,目的是实现您正在寻找的,叫做数据类。看一看。

使用类而不是听写是一个优先事项。就我个人而言,我更喜欢在事先不知道钥匙的情况下使用听写。(作为映射容器)。

使用类来保存数据意味着您可以为类属性提供文档。

就个人而言,使用类的最大原因可能是使用IDES自动完成功能!(从技术上讲是一个蹩脚的理由,但在实践中非常有用)


你的方法更好。不要太过期待未来,因为你不太可能成功。

但是,有时使用类似C结构的东西可能是有意义的,例如,如果您希望标识不同的类型,而不是对所有内容都使用dict。


顺便说一下,我认为实现@dataclass的python 3.7是实现类作为数据容器的最简单和最有效的方法。

1
2
3
4
5
6
7
8
9
10
@dataclass
class Data:
    a: list
    b: str    #default variables go after non default variables
    c: bool = False

def func():
    return A(a="hello")

print(func())

输出为:hello

它与类似scala的case类太相似,是将类用作容器的最简单方法。


您可以将dict和class的优点结合在一起,使用一些继承自dict的包装类。您不需要编写样板代码,同时可以使用点表示法。

1
2
3
4
5
6
7
8
class ObjDict(dict):
    def __getattr__(self,attr):
        return self[attr]
    def __setattr__(self,attr,value):
        self[attr]=value

self.group = ObjDict(a=1, b=2, c=3)
print self.group.a

我不同意在没有方法的类中使用代码更具可读性。您通常期望类提供功能,而不仅仅是数据。

所以,我将使用dict,直到出现对功能的需求,然后类的构造函数可以接收dict:-)


生产怎么样?

1
2
group = Prodict(a=1, b=2, c=3)
group.d = 4

如果您希望自动类型转换和自动代码完成(intelli-sense):

1
2
3
4
5
6
7
8
class Person(Prodict):
    name: str
    email: str
    rate: int

john = Person(name='John', email='[email protected]')
john.rate = 7
john.age = 35  # dynamic

听写显然适合这种情况。它是专门为那个用例设计的。除非你真的打算把这个类当作一个类来使用,否则没有必要重新设计轮子,造成额外的开销/浪费一个充当坏字典的类的空间(没有字典功能)。


在支持它的语言中,我将使用struct。字典最接近于Python中的结构,至少在我看来是这样。

更不用说,如果您真的想,您可以向字典中添加一个方法;)