关于python:我应该使用类还是字典?

Should I use a class or dictionary?

我有一个类,它只包含字段,不包含方法,如下所示:

1
2
3
4
5
6
7
8
9
class Request(object):

    def __init__(self, environ):
        self.environ = environ
        self.request_method = environ.get('REQUEST_METHOD', None)
        self.url_scheme = environ.get('wsgi.url_scheme', None)
        self.request_uri = wsgiref.util.request_uri(environ)
        self.path = environ.get('PATH_INFO', None)
        # ...

这可以很容易地翻译成dict。该类对于将来的添加更为灵活,并且可以与__slots__一起使用。那么使用听写会有什么好处呢?听写会比上课快吗?比有吃角子老虎的班快?


除非需要类的额外机制,否则使用字典。您也可以使用namedtuple进行混合方法:

1
2
3
4
5
6
7
>>> from collections import namedtuple
>>> request = namedtuple("Request","environ request_method url_scheme")
>>> request
<class '__main__.Request'>
>>> request.environ ="foo"
>>> request.environ
'foo'

这里的性能差异是最小的,尽管如果字典速度不快,我会感到惊讶。


python中的类是下面的dict。类行为确实会带来一些开销,但是如果没有分析器,您将无法注意到它。在这种情况下,我相信你会从课堂中受益,因为:

  • 你所有的逻辑都活在一个功能中
  • 它很容易更新并保持封装
  • 如果以后再更改任何内容,您可以轻松地保持界面不变。


你到底为什么要把这个编成字典?有什么优势?如果您以后想添加一些代码,会发生什么?你的__init__代码会去哪里?

类用于绑定相关数据(通常是代码)。

字典用于存储键值关系,通常键都属于同一类型,所有值也属于同一类型。有时,当键/属性名称不都是预先知道的时,它们可以用于捆绑数据,但这通常是您的设计有问题的迹象。

把这当作一门课。


我觉得每一个的用法对我来说都太主观了,我不想插手,所以我还是坚持数字。

我比较了在dict、一个新的_样式类和一个带槽的新_样式类中创建和更改变量所需的时间。

这是我用来测试它的代码(虽然有点乱,但它确实起到了作用)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import timeit

class Foo(object):

    def __init__(self):

        self.foo1 = 'test'
        self.foo2 = 'test'
        self.foo3 = 'test'

def create_dict():

    foo_dict = {}
    foo_dict['foo1'] = 'test'
    foo_dict['foo2'] = 'test'
    foo_dict['foo3'] = 'test'

    return foo_dict

class Bar(object):
    __slots__ = ['foo1', 'foo2', 'foo3']

    def __init__(self):

        self.foo1 = 'test'
        self.foo2 = 'test'
        self.foo3 = 'test'

tmit = timeit.timeit

print 'Creating...
'

print 'Dict: ' + str(tmit('create_dict()', 'from __main__ import create_dict'))
print 'Class: ' + str(tmit('Foo()', 'from __main__ import Foo'))
print 'Class with slots: ' + str(tmit('Bar()', 'from __main__ import Bar'))

print '
Changing a variable...
'


print 'Dict: ' + str((tmit('create_dict()[\'foo3\'] ="Changed"', 'from __main__ import create_dict') - tmit('create_dict()', 'from __main__ import create_dict')))
print 'Class: ' + str((tmit('Foo().foo3 ="Changed"', 'from __main__ import Foo') - tmit('Foo()', 'from __main__ import Foo')))
print 'Class with slots: ' + str((tmit('Bar().foo3 ="Changed"', 'from __main__ import Bar') - tmit('Bar()', 'from __main__ import Bar')))

这里是输出…

创建。。。

1
2
3
Dict: 0.817466186345
Class: 1.60829183597
Class_with_slots: 1.28776730003

正在更改变量…

1
2
3
Dict: 0.0735140918748
Class: 0.111714198313
Class_with_slots: 0.10618612142

所以,如果你只是在存储变量,你需要速度,而且不需要你做很多计算,我建议你使用dict(你总是可以做一个看起来像方法的函数)。但是,如果你真的需要课程,记住-总是使用槽。

注:

我用新的和旧的"类"来测试"类"。原来,旧的_样式的类创建速度更快,修改速度较慢(如果您在紧密循环中创建了许多类,那么这一点不重要(提示:您做错了))。

另外,在你的计算机上创建和更改变量的时间可能会有所不同,因为我的变量既旧又慢。确保你自己测试它以看到"真实"的结果。

编辑:

后来我测试了这个名字:我不能修改它,但是要创建10000个样本(或类似的东西),需要1.4秒,所以字典确实是最快的。

如果我将dict函数更改为包含键和值,并在创建dict时返回dict而不是包含dict的变量,那么它会给我0.65秒而不是0.8秒。

1
2
class Foo(dict):
    pass

创建就像一个带有槽的类,而更改变量是最慢的(0.17秒),所以不要使用这些类。选择dict(speed)或从object派生的类("syntax candy")。


我同意@adw。我绝不会用字典来表示一个"对象"(在OO意义上)。字典聚合名称/值对。类表示对象。我见过用字典表示物体的代码,不清楚物体的实际形状。当某些名称/值不存在时会发生什么?什么限制了客户将任何东西放入。或者想把任何东西弄出来。事物的形状应该总是清晰的定义。

当使用Python时,使用规则进行构建是很重要的,因为语言允许作者通过多种方式向自己的脚开枪。


我会推荐一个类,因为它是与请求相关的各种信息。如果要使用字典,我希望存储的数据在本质上更相似。我自己倾向于遵循的一条准则是,如果我想循环遍历整组键-值对并做一些事情,我就使用字典。否则,数据显然比基本的键-值映射具有更多的结构,这意味着类可能是更好的选择。

因此,坚持上课。


如果你想实现的只是像obj.bla = 5这样的语法糖果而不是obj['bla'] = 5,特别是如果你要重复很多,你可能会像martineaus建议中那样使用一些简单的容器类。然而,那里的代码非常膨胀,而且速度非常慢。你可以这样简单:

1
2
3
4
5
class AttrDict(dict):
   """ Syntax candy"""
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

切换到namedtuple或使用__slots__的类的另一个原因可能是内存使用。听写比列表类型需要更多的内存,因此这可能是需要考虑的一点。

不管怎样,在您的特定情况下,似乎没有任何动机可以从您当前的实现中切换出来。您似乎没有维护数以百万计的这些对象,因此不需要列表派生类型。它实际上包含了__init__中的一些功能逻辑,所以您也不应该使用AttrDict


也可以吃你的蛋糕。换句话说,您可以创建同时提供类和字典实例功能的东西。看到活动区了吗???????????????????????????????-S???????????SS配方和对方法的评论。

如果您决定使用常规类而不是子类,那么我找到了t?S??????????????????"????????????????????????????????????S????"?"???SS配方(由Alex Martelli设计)非常灵活,对你正在做的事情非常有用(即创建一个相对简单的信息聚合器)。因为它是一个类,您可以通过添加方法轻松地进一步扩展它的功能。

最后,应该注意的是,类成员的名称必须是合法的python标识符,但是字典键没有这样做,因此字典在这方面提供了更大的自由度,因为键可以是任何可哈希的(即使是不是字符串的东西)。

更新

在python 3.3中添加了一个名为SimpleNamespace的类object(它没有__dict__子类(它确实有一个子类),这是另一种选择。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ClassWithSlotBase:
    __slots__ = ('a', 'b',)

def __init__(self):
    self.a: str ="test"
    self.b: float = 0.0


def test_type_hint(_b: float) -> None:
    print(_b)


class_tmp = ClassWithSlotBase()

test_type_hint(class_tmp.a)

我推荐一门课。如果使用类,则可以获取类型提示,如图所示。当类是函数的参数时,类支持自动完成。

enter image description here