我应该使用类还是字典?

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

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 有什么好处吗?一个字典会比一堂课快吗?而且比有老虎机的班级还快?

80307 次浏览

除非需要类的额外机制,否则请使用字典。你也可以使用 namedtuple作为混合方法:

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

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

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

  • 你所有的逻辑都存在于一个函数中
  • 它很容易更新并保持封装
  • 如果您稍后更改了任何内容,则可以轻松地保持接口不变

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

因此,坚持上课。

你为什么要把这个做成字典?有什么好处?如果您稍后想要添加一些代码,会发生什么?你的 __init__代码会去哪里?

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

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

继续上课。

我同意@adw。我永远不会用字典来表示一个“对象”(在面向对象的意义上)。字典聚合名称/值对。类代表对象。我见过用字典表示对象的代码,但不清楚对象的实际形状是什么。当某些名称/值不存在时会发生什么?什么限制了客户投入任何东西。或者试图把任何东西弄出来。事物的形状应该始终是清晰的。

在使用 Python 时,有纪律地构建非常重要,因为该语言允许作者以多种方式搬起石头砸自己的脚。

也许你可以既吃蛋糕又吃蛋糕。换句话说,您可以创建一些同时提供类和字典实例功能的东西。请参阅 ActiveState 的 D something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something菜谱以及关于实现方法的注释。

如果你决定使用一个常规类而不是一个子类,我发现 Something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something something菜谱(Alex Martelli 编写)对于你正在做的事情(比如创建一个相对简单的信息聚合器)来说是非常灵活和有用的。因为它是一个类,所以您可以通过添加方法轻松地进一步扩展它的功能。

最后应该注意的是,类成员的名称必须是合法的 Python 标识符,但是字典键不是这样的ーー因此字典在这方面将提供更大的自由度,因为键可以是任何散列(即使不是字符串)。

更新

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

如果您想要实现的只是像 obj.bla = 5而不是 obj['bla'] = 5这样的语法糖果,特别是如果您必须经常重复这种语法糖果,那么您可能需要像 martineaus 建议那样使用一些普通容器类。尽管如此,那里的代码还是相当臃肿和不必要的慢。你可以这么简单:

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

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

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

我觉得每一个的用法对我来说都太主观了,所以我还是用数字吧。

我比较了创建和更改 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 (你总是可以只创建一个函数,看起来像一个方法)。但是,如果您真的需要类,请记住——始终使用 _ _

注:

我用 都有 new _ style 和 old _ style 类测试了“ Class”。事实证明,old _ style 类的创建速度更快,而修改速度更慢(如果您在一个紧凑的循环中创建了大量类,那么修改速度虽然不是很快,但意义重大(提示: 您做错了))。

此外,创建和更改变量的时间可能在您的计算机上不同,因为我的是旧的和缓慢的。请确保您自己测试它,以看到“真正的”结果。

编辑:

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

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

class Foo(dict):
pass

创建类似于带有插槽的类,改变变量是最慢的(0.17秒) ,因此 不要使用这些类。去为一个字典(速度)或类派生的对象(’语法糖果’)

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

如果数据,我的意思是字段集,在将来不会被改变或扩展,我会选择一个类来表示这样的数据。为什么?

  1. 更干净,更易读。
  2. 它在使用它方面更快,这比创建它重要得多,创建它通常只发生一次。

甚至更快似乎只使用类作为字段的容器而不是类的对象。

扩展 alexpinho98的例子:

import timeit


class Foo(object):


def __init__(self):


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


class FooClass:
foo1 = 'test'
foo2 = 'test'
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


dict = create_dict()
def testDict():
a = dict['foo1']
b = dict['foo2']
c = dict['foo3']


dict_obj = Foo()
def testObjClass():
a = dict_obj.foo1
b = dict_obj.foo2
c = dict_obj.foo3


def testClass():
a = FooClass.foo1
b = FooClass.foo2
c = FooClass.foo3




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 ('=== Testing usage 1 ===')
print ('Using dict  : ' + str(tmit('testDict()', 'from __main__ import testDict')))
print ('Using object: ' + str(tmit('testObjClass()', 'from __main__ import testObjClass')))
print ('Using class : ' + str(tmit('FooClass()', 'from __main__ import FooClass')))

结果如下:

Creating...


Dict: 0.185864600000059
Class: 0.30627199999980803
Class with slots: 0.2572166999998444
=== Testing usage 1 ===
Using dict  : 0.16507520000050135
Using object: 0.1266871000007086
Using class : 0.06327920000148879