在Python中更喜欢使用字典而非对象

3 投票
4 回答
590 浏览
提问于 2025-04-17 00:29

在Python中,使用字典而不是对象(或者反过来)来描述某个事物的属性,有什么好处吗?

我现在正在做的项目中,有很多地方使用了字典,而我本来会选择创建对象。在我看来,对象提供了更多的结构,并且可以通过像pylint这样的程序更好地检查程序员的错误,但我很难解释为什么我会选择对象而不是字典。

举个简单的例子,一个模块创建了“Widget”(小部件),并包含一个这样的函数:

def create(self, propertyA, propertyB=55, propertyC="default", 
           propertyD=None, propertyE=None, propertyF=None, propertyG=None,
           propertyH=None, propertyI=None):

这个函数会通过创建一个字典并像这样传入来调用:

widget_client = WidgetClient()
widget = {
    "propertyA": "my_widget",
    "propertyB": 10,
    ...
}
widget_client.create(**widget)

当我看到这个时,我意识到这些属性都是在描述一个“Widget”,我想做以下事情:

class Widget(object):
    """Represents a widget."""

    def __init__(self, propertyA, **kwargs):
        """Initialize a Widget.

        :param propertyA: The name of the widget.
        :param kwargs: Additional properties may be specified (see below).
        :returns: None

        """
        self.propertyA = propertyA
        self.propertyB = kwargs.get("propertyB", 55)
        self.propertyC = kwargs.get("propertyC", "default")
        self.propertyD = kwargs.get("propertyD", None)
        self.propertyE = kwargs.get("propertyE", None)
        self.propertyF = kwargs.get("propertyF", None)

然后更新create()方法,使其看起来像这样:

def create(self, widget):

最后调用的方式是这样的:

widget_client = WidgetClient()
widget = Widget(propertyA="my_widget")
widget.propertyB = 10
...
widget_client.create(widget)

在我看来,这显然更好,但我过去也曾错过,所以我不知道该如何解释自己。当然,我仍然在使用**kwargs,这可以通过将Widget拆分成更小的相关部分并创建更多对象来避免等等,但我觉得这算是一个好的“第一步”。这样说有道理吗?

字典的好处

  1. 速度更快和/或更节省内存

字典的缺点

  1. 无法通过静态代码检查器捕捉到某些错误
  2. 可能永远无法列出或知道所有小部件的属性

对象的好处

  1. 确切知道一个“Widget”由什么组成
  2. 有可能通过静态代码检查器捕捉到错误(尽管使用**这种魔法会阻止部分检查)

对象的缺点

  1. 速度较慢和/或占用更多内存

这似乎是个傻问题,但为什么要用对象做一些可以用字典完成的事情呢?

4 个回答

1

你可以用namedtuple来很好地实现这个功能。例如,你可以创建一个叫做Widget的namedtuple,并给它一些默认值:

>>> from collections import namedtuple
>>> _Widget = namedtuple("Widget", "propertyA propertyB propertyC propertyD propertyE propertyF propertyG propertyH propertyI")
>>> DefaultWidget = _Widget(None, 55, "Default", None, None, None, None, None, None)
>>> DefaultWidget
Widget(propertyA=None, propertyB=55, propertyC='Default', propertyD=None, propertyE=None, propertyF=None, propertyG=None, propertyH=None, propertyI=None)

接着,你可以写一个叫做Widget的函数,用来初始化这些属性:

def Widget(propertyA, **kwargs):
   return DefaultWidget._replace(propertyA=propertyA, **kwargs)

然后你可以这样使用它:

>>> Widget("test", propertyE=17)
Widget(propertyA='test', propertyB=55, propertyC='Default', propertyD=None, propertyE=17, propertyF=None, propertyG=None, propertyH=None, propertyI=None)

注意,如果你试图省略必填的propertyA:

>>> Widget()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Widget() takes exactly 1 argument (0 given)

或者给一个不存在的属性:

>>> Widget("test", propertyZ="test2")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in Widget
  File "<string>", line 32, in _replace
ValueError: Got unexpected field names: ['propertyZ']

它会以一种很好的方式处理这些情况。我觉得使用namedtuple可以解决你用字典时遇到的一些问题。

2

使用内置的数据类型总是有一些好处,比如它们自带的一些功能,而且其他程序员对它们的行为也很熟悉。比如字典,它提供了很多内置的方法,大家都知道它是可以被遍历的。

这只是一个好处。我并不是说你应该总是用字典,而不是自己定义对象。当然,你自己定义的对象也可以有类似字典的行为。但在某些情况下,如果简单的存储方式就能满足需求,那就没必要总是创建一个新对象。具体要看你的“Widget”是否有特别的行为或属性。

2

不,使用字典而不是对象没有什么好处——对象中的数据通常是存储在字典里的。

使用对象而不是字典可能会有一些好处。你可以看看这个链接了解更多:http://docs.python.org/reference/datamodel.html#slots

撰写回答