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文档中提到了其他数据容器类型(请参见下面的链接)。
下面是关于将
选项
标准库中的备选方案
collections.namedtuple :具有属性的元组(参见种子配方)typing.NamedTuple 子类tuple(见本帖与namedtuple 比较)types.SimpleNamespace :简单类w/可选类声明types.MappingProxy :只读dictenum.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()) |
输出为:
它与类似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 |
听写显然适合这种情况。它是专门为那个用例设计的。除非你真的打算把这个类当作一个类来使用,否则没有必要重新设计轮子,造成额外的开销/浪费一个充当坏字典的类的空间(没有字典功能)。
在支持它的语言中,我将使用
更不用说,如果您真的想,您可以向字典中添加一个方法;)