关于设计模式:首选Python中的对象字典

Preferring dictionaries over objects in Python

在python中使用字典而不是对象(反之亦然),当您所做的只是描述某个对象的属性时,是否有好处?

我正在研究的项目目前有很多地方使用字典,我通常会在那里创建对象。在我看来,对象提供了更多的结构,允许更好的程序员通过程序(如pylint)进行错误检查,但是很难解释为什么我要使用对象而不是dict。

对于模拟示例,一个模块创建小部件并包含如下方法:

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

通过创建一个字典并像这样传递它,可以调用该方法:

1
2
3
4
5
6
7
widget_client = WidgetClient()
widget = {
   "propertyA":"my_widget",
   "propertyB": 10,
    ...
}
widget_client.create(**widget)

当我看到这一点时,我发现这些属性中的每一个都是描述"小部件"的属性,并希望执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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()方法如下:

1
def create(self, widget):

最后被这样称呼:

1
2
3
4
5
widget_client = WidgetClient()
widget = Widget(propertyA="my_widget")
widget.propertyB = 10
...
widget_client.create(widget)

在我看来,这显然更好,但我过去犯了错误,我想不出如何解释自己。当然,我仍然在使用**Kwarg,这可以通过将小部件分解成更小的组件/相关部件,创建更多的对象等来避免,但我觉得这是一个很好的"第一步"。这有什么意义吗?

字典的好处:

  • 更快和/或更节省内存
  • 字典缺点:

  • 无法用静态代码检查程序捕获某些错误
  • 所有小部件属性的完整列表可能永远不会出现或已知
  • 对象优势:

  • 确切地知道"小部件"是由什么组成的
  • 使用静态代码检查程序可能会捕获错误(尽管使用**magic可以防止某些错误)
  • 对象缺点:

  • 速度较慢和/或内存效率较低
  • 这似乎是一个愚蠢的问题,但为什么要对可以用字典完成的对象做些什么呢?


    使用任何内置数据类型都会给您带来某些功能的优势,而且它的行为对于其他程序员来说是众所周知的。字典给你一个充满内置方法的拳头,没有人会怀疑它是否是不可测的。

    这只是一个优势。我不是说你应该总是使用字典来声明你自己的对象。(当然,您的新对象可以继承类似字典的行为),但当一个更简单的存储机制可以做到时,您不一定总是选择创建一个新对象。使用理解作为指导,它将取决于小部件是否有任何特殊的行为或属性。


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

    使用对象而不是字典可能会有好处。见:http://docs.python.org/reference/datamodel.html插槽


    您可以使用NamedDuple很好地实现这一点。例如,可以使用默认值创建一个名为dtuple的小部件:

    1
    2
    3
    4
    5
    >>> 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的函数来初始化属性:

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

    然后你可以这样使用它:

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

    请注意,如果您试图忽略所需的属性A:

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

    或者如果您提供了不存在的属性:

    1
    2
    3
    4
    5
    6
    >>> 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可以消除你使用字典的缺点。


    我倾向于使用对象。我的理由是它们更容易扩展。如果人们通过字段访问对象,如果需要其他功能,这些字段可以成为属性。如果他们正在访问密钥,那么很难在不更改接口的情况下添加额外的逻辑。