我应该使用课程还是字典?


99

我有一个只包含字段而没有方法的类,如下所示:

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)
        # ...

这可以很容易地翻译成字典。该类对于将来的添加更加灵活,使用可以更快__slots__。那么使用dict会有好处吗?字典会比全班更快吗?并且比具有插槽的课程快吗?


2
我总是使用字典来保存数据,这对我来说似乎是一个用例。在某些情况下,从派生类dict可能是有道理的。巧妙的好处:调试时,只需说一下print(request),您就可以很容易地看到所有状态信息。使用更经典的方法,您将必须编写自定义__str__方法,如果您始终需要这样做,这会很麻烦。


如果这堂课是完全有意义的,并且对别人很清楚,那为什么不呢?此外,例如,如果使用公共接口定义许多类,为什么不呢?但是Python不支持强大的面向对象概念,例如C ++。
MasterControlProgram

3
@Ralf python不支持哪些OOP?
qwr

Answers:


32

你为什么要把它当作字典?有什么好处?如果您以后想要添加一些代码,会发生什么?您的__init__代码会去哪儿?

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

字典用于存储键-值关系,其中通常键都是同一类型,并且所有值也都是一种类型。有时,当键/属性名称并非一开始就为人所知时,它们对于捆绑数据很有用,但这通常表明您的设计有问题。

保持这堂课。


我将创建一种工厂方法,该方法创建字典而不是类的__init__方法。但是你是对的:我会分开属于一起的事物。
守护进程

88
再也无法与您不同:字典,集合,列表和元组都可以捆绑相关数据。绝对不存在字典值应该或必须具有相同数据类型的假设,恰恰相反。在许多列表和集合中,值将具有相同的类型,但这主要是因为我们希望将其一起枚举。实际上,我确实认为广泛使用类来保存数据是对oop的滥用。当您思考序列化问题时,您可以轻松地了解原因。

4
它是面向对象的编程,而不是面向类的,原因是:我们处理对象。对象的特征在于2(3)个属性:1.状态(成员)2.行为(方法)和3.实例可以用几个词来描述。因此,类用于将状态和行为捆绑在一起。
friendzis

14
我将其标记为下来,因为在可能的情况下,您应该始终默认使用更简单的数据结构。在这种情况下,字典足以满足预期的目的。这个问题where would your __init__ code go?有关。可以说服经验较少的开发人员,因为在字典中不使用init方法,所以只使用类。荒诞。
劳埃德·摩尔

1
@Ralf Foo类只是一个类型,如int和string。是否将值存储在整数类型或整数类型的变量foo中?细微但重要的语义差异。在像C这样的语言中,这种区别在学术界之外不太重要。尽管大多数OO语言都支持类变量,这使得类与对象/实例之间的区别极为相关-您是将数据存储在类中(在所有实例之间共享)还是存储在特定对象中?别被咬了
朋友友'17

44

使用字典,除非您需要类的额外机制。您还可以将a namedtuple用作混合方法:

>>> from collections import namedtuple
>>> request = namedtuple("Request", "environ request_method url_scheme")
>>> request
<class '__main__.Request'>
>>> request.environ = "foo"
>>> request.environ
'foo'

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


15
“这里的性能差异将是最小的,尽管如果字典的速度没有明显提高,我会感到惊讶。” 这不会计算。:)
mipadi

1
@mipadi:的确如此。现在已解决:p
Katriel 2010年

dict比namedtuple快1.5倍,是没有槽的类的两倍。检查我关于这个答案的帖子。
alexpinho98 2013年

@ alexpinho98:我已经尽力找到您所指的“帖子”,但是我找不到它!您可以提供网址吗?谢谢!
Dan Oblinger

@DanOblinger我假设他在下面表示他的答案。
亚当·刘易斯

37

python 的类下面的字典。类的行为确实会增加一些开销,但是如果没有事件探查器,您将无法注意到它。在这种情况下,我相信您会从课堂中受益,因为:

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

您的所有逻辑都不存在于一个函数中。类是共享状态的元组,通常是一个或多个方法。如果更改类,则不会保证其接口。
劳埃德·摩尔

25

我认为每个人的用法都太主观,我无法理解,所以我只会坚持数字。

我比较了在dict,new_style类和带槽的new_style类中创建和更改变量所需的时间。

这是我用来测试的代码(虽然有点杂乱,但确实可以完成工作。)

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...\n'
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 '\nChanging a variable...\n'

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')))

这是输出...

正在建立...

Dict: 0.817466186345
Class: 1.60829183597
Class_with_slots: 1.28776730003

更改变量...

Dict: 0.0735140918748
Class: 0.111714198313
Class_with_slots: 0.10618612142

因此,如果您只是存储变量,则需要速度,并且不需要进行很多计算,因此我建议使用dict(您始终可以使函数看起来像方法)。但是,如果您确实需要类,请记住-始终使用__ slot __

注意:

我测试的“类”有两种 new_style和old_style类。事实证明,old_style类的创建速度更快,但修改速度却较慢(如果要在紧密的循环中创建许多类,则幅度不大,但意义重大(提示:您做错了))。

此外,由于我的计算机较旧且运行缓慢,因此在计算机上创建和更改变量的时间可能会有所不同。确保自己进行测试以查看“真实”结果。

编辑:

后来我测试了namedtuple:我无法修改它,但是创建10000个样本(或类似的东西)花了1.4秒,因此字典确实是最快的。

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

class Foo(dict):
    pass

创建就像是一个带有插槽的类,并且更改变量最慢(0.17秒),因此不要使用这些类。求字典(速度)或对象派生的类(“语法糖果”)


我想查看dict()没有子类的子类的编号。它的执行方式是否与从头开始编写的新型类相同?
本杰明·霍奇森

12

我同意@adw。我永远不会用字典来代表“对象”(从OO意义上来说)。词典汇总名称/值对。类代表对象。我已经看到了用字典表示对象的代码,目前尚不清楚事物的实际形状是什么。当某些名称/值不存在时会发生什么?是什么限制了客户什么也没花。或者试图把所有东西都花掉。事物的形状应始终明确定义。

使用Python时,重要的是要有纪律性进行构建,因为该语言为作者提供了多种射击方式。


9
关于SO的答案按投票排序,具有相同投票数的答案是随机排序的。因此,请澄清“最后的海报”是指谁。
Mike DeSimone 2010年

4
从OO的角度来看,您怎么知道只有字段却没有功能的东西是“对象”?
Muhammad Alkarouri,2010年

1
我的试金石测试是“此数据的结构是否固定?”。如果是,则使用对象,否则使用字典。背离这一点只会造成混乱。
weberc2

您必须分析要解决的问题的背景。熟悉该景观后,对象应该相对明显。我个人认为返回字典应该是您的最后选择,除非您返回的应该是名称/值对的映射。我看过太多草率的代码,其中传入和传出方法的所有内容都只是一本字典。很懒 我喜欢动态类型的语言,但是我也喜欢我的代码清晰,逻辑。将所有内容都塞进字典可能是隐藏含义的便利。
jaydel

5

我会推荐一个类,因为它是与请求有关的各种信息。曾经是使用字典的人,我希望存储的数据本质上会更加相似。我倾向于遵循的一个指导原则是,如果我想遍历整个键-值对集合并执行某些操作,则可以使用字典。否则,数据显然比基本的键->值映射具有更多的结构,这意味着类可能是更好的选择。

因此,坚持上课。


2
我完全不同意。没有理由将字典的使用限制为要迭代的事物。它们用于维护映射。
卡特里尔

1
请仔细阅读。我说可能要循环,而不是。对我来说,使用字典意味着键和值之间存在很大的功能相似性。例如,带有年龄的名称。使用类将意味着各种“键”的值具有截然不同的含义。例如,参加PErson课程。在这里,“名称”将是一个字符串,“朋友”可能是列表,字典或其他合适的对象。在此类的常规用法中,您不会遍历所有这些属性。
柱头

1
我认为在Python中,类和词典之间的区别是不明显的,因为前者是使用后者实现的(不能忍受“槽”)。我第一次学习该语言时,我就感到有点困惑(以及类是对象,因此是一些神秘的元类实例的事实)。
martineau,2010年

4

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

class AttrDict(dict):
    """ Syntax candy """
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

切换到namedtuples或class的另一个原因__slots__可能是内存使用率。字典比列表类型需要更多的内存,因此可能需要考虑一下。

无论如何,在您的特定情况下,似乎没有任何动力要退出当前的实现。您似乎没有维护数百万个此类对象,因此不需要列表派生类型。而且它实际上包含内的一些功能逻辑__init__,因此您也不应该使用AttrDict


types.SimpleNamespace(自Python 3.3起可用)是自定义AttrDict的替代方法。
Cristian Ciupitu,

4

也可能有蛋糕也可以吃。换句话说,您可以创建提供类和字典实例功能的东西。请参阅ActiveState的Dɪᴄᴛɪᴏɴᴀʀʏᴡɪᴛʜᴀᴛᴛʀɪʙᴜᴛᴇ-sᴛʏʟᴇss食谱和有关此方法的注释。

如果您决定使用常规类而不是子类,那么我发现T recipesɪᴍᴘʟᴇᴄᴏʟʟᴇᴄᴛᴏʀᴄᴏʟʟᴇᴄᴛᴏʀᴄᴏʟʟᴇᴄᴛᴏʀrecipe recipe ss的食谱(由Alex Martelli 撰写非常灵活,对此类事情很有用看起来就像您在做的(即创建一个相对简单的信息聚合器)。由于它是一个类,因此您可以通过添加方法轻松地进一步扩展其功能。

最后,应该指出,类成员的名称必须是合法的Python标识符,但字典键则不能—因此字典在这方面将提供更大的自由度,因为键可以是任何可散列的东西(甚至可以不是字符串)。

更新资料

一类object(其不具有__dict__)指定的子类SimpleNamespace(它有一个)加入到该types模块的Python 3.3,并且是又一替代。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.