在Python中,使用“dict”与关键字还是匿名字典?
假设你想把一组值传递给一个函数,或者你想使用一个短暂的字典,这个字典不会被重复使用。其实有两种简单的方法可以做到这一点:
第一种是使用 dict()
函数来创建一个字典:
foo.update(dict(bar=42, baz='qux'))
第二种是使用一个匿名字典:
foo.update({'bar': 42, 'baz': 'qux'})
你更喜欢哪种方法呢?除了个人风格之外,还有其他原因让你选择其中一种吗?
9 个回答
我的回答主要会讨论在设计API时使用字典和关键字参数的选择。不过,这也适用于单独使用{...}
和dict(...)
的情况。
最重要的一点是:保持一致。如果你的代码大部分地方把'bar'
当作字符串使用,那就把它保持为字符串形式{...}
;如果你通常把它当作标识符bar
来用,那就用dict(bar=...)
。
限制条件
在谈论风格之前,先说明一下,关键字bar=42
的语法只适用于字符串,并且这些字符串必须是有效的标识符。如果你需要使用任意的标点符号、空格、Unicode字符,甚至非字符串的键,那就没得选了,只有{'bar': 42}
这种语法可以用。
这也意味着,在设计API时,你必须允许使用完整的字典,而不仅仅是关键字参数——除非你确定只允许字符串,并且这些字符串都是有效的标识符。(从技术上讲,update(**{'spaces & punctuation': 42})
是可以的,但看起来很糟糕。而数字、元组和Unicode则无法使用。)
要注意的是,dict()
和dict.update()
结合了这两种API:你可以传递一个完整的字典,也可以传递关键字参数,甚至可以同时传递两者(后者我认为没有文档说明)。所以如果你想让使用者更方便,最好同时支持这两种方式:
def update(self, *args, **kwargs):
"""Callable as dict() - with either a mapping or keyword args:
.update(mapping)
.update(**kwargs)
"""
mapping = dict(*args, **kwargs)
# do something with `mapping`...
这对于一个名为.update()
的方法尤其推荐,以遵循“最少惊讶”原则。
风格
我觉得区分内部字符串和外部字符串是很有必要的。内部字符串是指在程序内部使用的任意标识符(比如变量名、对象属性),或者在多个程序之间使用的(比如数据库列名、XML属性名)。这些通常只对开发者可见。而外部字符串是给人看的。
[一些Python程序员(包括我)遵循的约定是:内部字符串用'单引号'
,外部字符串用"双引号"
。不过这并不是普遍适用的规则。]
你的问题是关于裸字(Perl术语)的正确使用——这是一种语法糖,允许在内部字符串中省略引号。一些语言(尤其是LISP)广泛支持这种用法;在Python中,使用裸字的机会主要是在属性访问foo.bar
和关键字参数update(bar=...)
。
这里的风格困惑是:“你的字符串内部到足以看起来像标识符吗?”
如果键是外部字符串,答案肯定是否定的:
foo.update({"The answer to the big question": 42})
# which you later might access as:
foo["The answer to the big question"]
如果键指的是Python标识符(例如对象属性),那么我会说是肯定的:
foo.update(dict(bar=42))
# As others mentioned, in that case the cleaner API (if possible)
# would be to receive them as **kwargs directly:
foo.update(bar=42)
# which you later might access as:
foo.bar
如果键指的是你Python程序外部的标识符,比如XML属性名或数据库列名,使用裸字可能是好选择,也可能是坏选择——但最好选择一种风格并保持一致性。
保持一致性是好的,因为在标识符和字符串之间存在心理障碍。这个障碍的存在是因为字符串很少跨越它——只有在使用反射进行元编程时才会。语法高亮也会加强这种感觉。所以如果你在代码中看到一个绿色的'bar'
和一个黑色的foo.bar
,你可能不会立刻联想到它们之间的关系。
另一个重要的经验法则是:裸字在大多数情况下是固定的才是好的。例如,如果你在代码中大部分时间都引用固定的数据库列,那么使用裸字来引用它们可能不错;但如果一半时间这个列是一个参数,那就最好使用字符串。
这是因为参数和常量是人们与标识符/字符串障碍之间最重要的区别。column
(变量)和"person"
(常量)之间的区别是传达这种差异最清晰的方式。如果两者都是标识符,就会模糊这个区别,并且在语法上也会反弹——你会需要频繁使用**{column: value}
和getattr(obj, column)
等。
在这个特定的情况下,我可能更喜欢这样:
foo.update(bar=42, baz='qux')
在更一般的情况下,我通常更喜欢字面量语法(你称之为匿名字典,其实用 {}
和 dict()
使用起来都是一样匿名的)。我觉得这样对维护代码的人(通常是我)来说更清晰,部分原因是它在语法高亮的文本编辑器中显得特别明显。这样一来,当我需要添加一个不能用Python名字表示的键,比如带空格的键时,我就不需要重写整行代码了。
我更喜欢使用匿名字典的方式。
我不喜欢用dict()
这个方式,原因和我不喜欢以下内容是一样的:
i = int("1")
用dict()
这个方式,你是在多此一举地调用一个函数,这样会增加一些不必要的负担:
>>> from timeit import Timer
>>> Timer("mydict = {'a' : 1, 'b' : 2, 'c' : 'three'}").timeit()
0.91826782454194589
>>> Timer("mydict = dict(a=1, b=2, c='three')").timeit()
1.9494664824719337