为什么对空 list/dict 的断言失败?
Why an assertion on an empty list/dict fails?
assert {}
失败 AssertionError
。
导致这个问题的原因如下:
import xml.etree.ElementTree as ET
xml_element = ET.Element("tag")
assert xml_element
也因 AssertionError
而失败。
assert
评估 expression
,我明白了。
还有;
if []:
print("foo")
不会打印任何内容,因为列表为空 returns False
但是,
if [1]:
print("foo")
会打印 foo.
我的问题是如何检查此行为的内部结构?
什么时候正常转换为true/false?
您真正要问的问题是“将列表或字典转换为 bool 时会发生什么?”。这是因为 if x:
和 assert x
的行为与您编写 if bool(x):
或 assert bool(x)
的行为相同。 (顺便说一句,在那些地方显式调用 bool()
是错误的形式,正是因为它是多余的。)
答案是列表和字典(以及字符串和其他容器)在转换为 bool
时 True
如果它们 不为空 。相反,如果它们是 empty.
,则在转换为 bool
时它们是 False
经常使用的术语是 non-empty 容器是“真实的”,空容器是“虚假的”。
这有一个有趣的结果,即 bool("False")
计算为 True
- 因为 "False"
是一个 non-empty 字符串!
除了容器之外,还有其他一些常见情况:如果数字 non-zero 则为真(即 27
和 0.5
为真,而 0
和0.0
是错误的),值 None
是错误的。
如何查找其他 classes
how could I have checked the internals of this behavior ?
如上所述,任何足够像集合的东西在 non-empty 时都是真实的;它是否“像一个容器”的通常测试是您是否可以在其上成功调用 len(x)
,在这种情况下,只要 len(x)
是 non-zero.
,它就是真实的
即使 class 不是容器(或数字),它仍然有可能对 bool()
进行一些有意义的转换。事实上,您甚至可以通过定义 __bool__()
方法在您自己的 classes 上定义它。确定这一点的唯一方法是查看您正在使用的库的文档,而不是查看源代码。
如果库没有定义这样的转换,那么 bool(x)
将总是 return True
(这是没有 [=31 的 classes 的默认值=] 或 __len__()
定义的方法)。有关更多信息,请参阅官方 Python 文档中的 Truth value testing。
特别是 ElementTree
xml.etree.ElementTree
特别是不幸的是,事情有点混乱。 A 看起来有点像元素的容器:e[0]
return 是第一个子元素,for child in e
return 是所有子元素,len(e)
returns 子元素的数量(这些都记录在 official docs 中)。因此,当至少有一个子元素时 bool(e)
恰好为真,这是完全合理的——这与 Python 的所有其余元素的工作方式相符。但是,正如 Tomerikoo 的答案在源代码中所示,虽然目前有效,但已被弃用,将来可能无法使用。文档也提到了这一点,但它非常隐蔽:
Caution: Elements with no subelements will test as False
. This behavior will change in future versions. Use specific len(elem)
or elem is None
test instead.
下面的代码片段解释了这背后令人困惑的逻辑。背景是,maybe_child = e.find("child")
找到名称为 "child"
的 e
的子元素,如果没有找到这样的元素,则为 None
。但是 None
是错误的,因此很容易通过编写 if maybe_child:
来检查此搜索是否成功 - 但如果找到了子元素但本身没有子元素,那实际上将算作 False
。这是文档中的片段:
element = root.find('foo')
if not element: # careful!
print("element not found, or element has no subelements")
if element is None:
print("element not found")
来自 ElementTree
's source code(属于 class Element
):
class Element:
[...]
def __init__(self, tag, attrib={}, **extra):
if not isinstance(attrib, dict):
raise TypeError("attrib must be dict, not %s" % (
attrib.__class__.__name__,))
self.tag = tag
self.attrib = {**attrib, **extra}
self._children = []
[...]
def __bool__(self):
warnings.warn(
"The behavior of this method will change in future versions. "
"Use specific 'len(elem)' or 'elem is not None' test instead.",
FutureWarning, stacklevel=2
)
return len(self._children) != 0 # emulate old behaviour, for now
因为你刚刚初始化了一个新元素,它的_children
是一个空列表(在__init__
中可以看到),所以如果你明白为什么assert []
失败了,你应该明白为什么 assert xml_element
失败 as-well.
assert {}
失败 AssertionError
。
导致这个问题的原因如下:
import xml.etree.ElementTree as ET
xml_element = ET.Element("tag")
assert xml_element
也因 AssertionError
而失败。
assert
评估 expression
,我明白了。
还有;
if []:
print("foo")
不会打印任何内容,因为列表为空 returns False
但是,
if [1]:
print("foo")
会打印 foo.
我的问题是如何检查此行为的内部结构?
什么时候正常转换为true/false?
您真正要问的问题是“将列表或字典转换为 bool 时会发生什么?”。这是因为 if x:
和 assert x
的行为与您编写 if bool(x):
或 assert bool(x)
的行为相同。 (顺便说一句,在那些地方显式调用 bool()
是错误的形式,正是因为它是多余的。)
答案是列表和字典(以及字符串和其他容器)在转换为 bool
时 True
如果它们 不为空 。相反,如果它们是 empty.
bool
时它们是 False
经常使用的术语是 non-empty 容器是“真实的”,空容器是“虚假的”。
这有一个有趣的结果,即 bool("False")
计算为 True
- 因为 "False"
是一个 non-empty 字符串!
除了容器之外,还有其他一些常见情况:如果数字 non-zero 则为真(即 27
和 0.5
为真,而 0
和0.0
是错误的),值 None
是错误的。
如何查找其他 classes
how could I have checked the internals of this behavior ?
如上所述,任何足够像集合的东西在 non-empty 时都是真实的;它是否“像一个容器”的通常测试是您是否可以在其上成功调用 len(x)
,在这种情况下,只要 len(x)
是 non-zero.
即使 class 不是容器(或数字),它仍然有可能对 bool()
进行一些有意义的转换。事实上,您甚至可以通过定义 __bool__()
方法在您自己的 classes 上定义它。确定这一点的唯一方法是查看您正在使用的库的文档,而不是查看源代码。
如果库没有定义这样的转换,那么 bool(x)
将总是 return True
(这是没有 [=31 的 classes 的默认值=] 或 __len__()
定义的方法)。有关更多信息,请参阅官方 Python 文档中的 Truth value testing。
特别是 ElementTree
xml.etree.ElementTree
特别是不幸的是,事情有点混乱。 A 看起来有点像元素的容器:e[0]
return 是第一个子元素,for child in e
return 是所有子元素,len(e)
returns 子元素的数量(这些都记录在 official docs 中)。因此,当至少有一个子元素时 bool(e)
恰好为真,这是完全合理的——这与 Python 的所有其余元素的工作方式相符。但是,正如 Tomerikoo 的答案在源代码中所示,虽然目前有效,但已被弃用,将来可能无法使用。文档也提到了这一点,但它非常隐蔽:
Caution: Elements with no subelements will test as
False
. This behavior will change in future versions. Use specificlen(elem)
orelem is None
test instead.
下面的代码片段解释了这背后令人困惑的逻辑。背景是,maybe_child = e.find("child")
找到名称为 "child"
的 e
的子元素,如果没有找到这样的元素,则为 None
。但是 None
是错误的,因此很容易通过编写 if maybe_child:
来检查此搜索是否成功 - 但如果找到了子元素但本身没有子元素,那实际上将算作 False
。这是文档中的片段:
element = root.find('foo')
if not element: # careful!
print("element not found, or element has no subelements")
if element is None:
print("element not found")
来自 ElementTree
's source code(属于 class Element
):
class Element:
[...]
def __init__(self, tag, attrib={}, **extra):
if not isinstance(attrib, dict):
raise TypeError("attrib must be dict, not %s" % (
attrib.__class__.__name__,))
self.tag = tag
self.attrib = {**attrib, **extra}
self._children = []
[...]
def __bool__(self):
warnings.warn(
"The behavior of this method will change in future versions. "
"Use specific 'len(elem)' or 'elem is not None' test instead.",
FutureWarning, stacklevel=2
)
return len(self._children) != 0 # emulate old behaviour, for now
因为你刚刚初始化了一个新元素,它的_children
是一个空列表(在__init__
中可以看到),所以如果你明白为什么assert []
失败了,你应该明白为什么 assert xml_element
失败 as-well.