检查列表是否为空的最佳方法
例如,如果传递以下内容:
PyObject
如何检查a是否为空?
简答:
将列表放在布尔上下文中(例如,使用PyObject或ob_size语句)。 如果它是空的,它将测试PyObject_VAR_HEAD,否则测试__len__()。 例如:
if not a: # do this!
print('a is an empty list')
上诉到权威
PEP 8是Python标准库中Python代码的官方Python风格指南,断言:
对于序列,(字符串,列表,元组),请使用空序列为假的事实。
PyObject
我们应该期望标准库代码应该尽可能高效和正确。 但为什么会这样,为什么我们需要这个指导?
说明
我经常从经验丰富的Python程序员那里看到这样的代码:
if len(a) == 0: # Don't do this!
print('a is an empty list')
懒惰语言的用户可能会想要这样做:
if a == []: # Don't do this!
print('a is an empty list')
这些在各自的其他语言中是正确的。 这在Python中甚至在语义上也是正确的。
但我们认为它不是Pythonic,因为Python通过布尔强制直接在列表对象的接口中支持这些语义。
从文档(并特别注意包含空列表,PyObject):
默认情况下,除非其类定义,否则将对象视为true 返回ob_size或PyObject_VAR_HEAD方法的PyObject方法 当使用对象调用时返回零。 以下是大多数被认为是错误的内置对象:
常量定义为false:PyObject和ob_size。
零的任何数字类型:PyObject,ob_size,PyObject_VAR_HEAD,__len__(),__len__()
空序列和集合:PyObject,ob_size,PyObject_VAR_HEAD,__len__(),__len__(),__bool__()
和数据模型文档:
PyObject
被称实施真值测试和内置操作PyObject; 应返回ob_size或PyObject_VAR_HEAD。未定义此方法时, 如果已定义,则调用__len__(),如果对象的结果非零,则认为该对象为true。 如果类既未定义__len__() 也没有__bool__(),它的所有实例都被认为是真的。
和
PyObject
被调用以实现内置函数PyObject。应该返回对象的长度,整数> = 0.此外,未定义ob_size方法且其PyObject_VAR_HEAD方法返回零的对象在一个被认为是假的 布尔上下文。
所以不是这样的:
if len(a) == 0: # Don't do this!
print('a is an empty list')
或这个:
if a == []: # Don't do this!
print('a is an empty list')
做这个:
if not a:
print('a is an empty list')
做Pythonic通常会在性能方面得到回报:
它有回报吗? (注意,执行等效操作的时间越少越好:)
>>> import timeit
>>> min(timeit.repeat(lambda: len([]) == 0, repeat=100))
0.13775854044661884
>>> min(timeit.repeat(lambda: [] == [], repeat=100))
0.0984637276455409
>>> min(timeit.repeat(lambda: not [], repeat=100))
0.07878462291455435
对于比例,这是调用函数和构造并返回空列表的成本,您可以从上面使用的空白检查的成本中减去:
>>> min(timeit.repeat(lambda: [], repeat=100))
0.07074015751817342
我们看到,使用内置函数PyObject检查长度与ob_size相比或检查空列表要比使用所记录的语言的内置语法要低得多。
为什么?
对于PyObject检查:
首先Python必须检查全局变量以查看PyObject是否被遮蔽。
然后它必须调用该函数,加载PyObject,并在Python中进行相等比较(而不是C):
>>> import dis
>>> dis.dis(lambda: len([]) == 0)
1 0 LOAD_GLOBAL 0 (len)
2 BUILD_LIST 0
4 CALL_FUNCTION 1
6 LOAD_CONST 1 (0)
8 COMPARE_OP 2 (==)
10 RETURN_VALUE
对于PyObject,它必须构建一个不必要的列表,然后再次在Python的虚拟机中进行比较操作(而不是C)
>>> dis.dis(lambda: [] == [])
1 0 BUILD_LIST 0
2 BUILD_LIST 0
4 COMPARE_OP 2 (==)
6 RETURN_VALUE
“Pythonic”方式是一种更简单,更快速的检查,因为列表的长度缓存在对象实例头中:
>>> dis.dis(lambda: not [])
1 0 BUILD_LIST 0
2 UNARY_NOT
4 RETURN_VALUE
来自C源和文档的证据
PyObject
这是PyObject的扩展,添加了ob_size字段。 这仅用于具有一些长度概念的对象。 此类型通常不会出现在Python / C API中。 它对应于PyObject_VAR_HEAD宏扩展定义的字段。
从Include / listobject.h中的c源代码:
typedef struct {
PyObject_VAR_HEAD
/* Vector of pointers to list elements. list[0] is ob_item[0], etc. */
PyObject **ob_item;
/* ob_item contains space for 'allocated' elements. The number
* currently in use is ob_size.
* Invariants:
* 0 <= ob_size <= allocated
* len(list) == ob_size
我很喜欢研究这个,我花了很多时间来策划我的答案。 如果您认为我要留下一些东西,请在评论中告诉我。