为啥要在 Python 中隐式检查是不是为空? [关闭]
Posted
技术标签:
【中文标题】为啥要在 Python 中隐式检查是不是为空? [关闭]【英文标题】:Why implicitly check for emptiness in Python? [closed]为什么要在 Python 中隐式检查是否为空? [关闭] 【发布时间】:2011-09-21 12:25:24 【问题描述】:Python 之禅说显式优于隐式。
然而,检查集合 c 是否为空的 Pythonic 方法是:
if not c:
# ...
并检查集合是否不为空,如下所示:
if c:
# ...
同样适用于任何可能具有“零”或“空”的事物(元组、整数、字符串、无等)
这样做的目的是什么?如果我不这样做,我的代码会变得更糟糕吗?或者它是否支持更多用例(即:某种多态性),因为人们可以覆盖这些布尔强制?
【问题讨论】:
您会提出什么明确、简单和可读的建议(Zen 的部分不止于此)? 我认为:if x == 0
可读性很强,而且没有歧义。
我希望这个问题没有结束。我实际上认为这是一个很好的选择,尽管 GNUnit 对此进行了不必要的争论。
如果这真的困扰你试试 Ruby
【参考方案1】:
如果您愿意,可以输入if len(lst) > 0
,但有什么意义呢? Python 确实使用 一些 约定,其中之一是空容器被认为是错误的。
您可以使用object.__nonzero__(self)
进行自定义:
调用实现真值 测试和内置操作
bool()
;应该返回False
或True
, 或它们的整数等价物0
或1
。 未定义此方法时,__len__()
被调用,如果是 定义,并且对象被认为是 如果其结果为非零,则为真。如果一个 类既不定义__len__()
也不定义__nonzero__()
,它的所有实例都是 认为是真的。
【讨论】:
【参考方案2】:这种最佳做法并非没有道理。
在测试if object:
时,你基本上是在调用对象__bool__
的方法,可以根据对象的行为重写和实现。
例如,集合上的__bool__
方法(Python 2 中的__nonzero__
)将根据集合是否为空返回一个布尔值。
(参考:http://docs.python.org/reference/datamodel.html)
【讨论】:
+1;这正是 OP 询问的“某种多态性”。 您只是解释了我的示例代码是如何工作的,而不是为什么if x
比if x == 0
更好。为什么集合不能只有一个 .length (如果它让你开心的话 .len )方法?它仍然是多态性,但更明确。这里的多态类型对我来说似乎不合理。整数实现与集合相同的接口(“falsyness”)。我能看到的唯一用例是实现一个函数f(x)
,其中 x 可以是整数或集合,因此该函数可以查看 x 是否为真/假,但在这种情况下,为什么不只传入一个布尔值呢?
@delman, x
是您回复的评论中的整数。此外,我提到的用例表明我想不出这种多态性的有效用例。我创建了一个函数,它只能知道它的类型具有空属性,但它不可能做任何其他事情,因为除了显而易见的事情之外,没有太多其他理智的事情可以在整数和集合上完成比如散列。
选择在集合类型上定义__nonzero__
以返回len(self) != 0
是经过深思熟虑的选择,这当然不是偶然的。它是显式的(如果没有定义该方法,行为将不会出现),它是直观的(空集合也称为空集,null 是 false 的另一个名称),它很有用(更少输入最常见的类型集合的谓词)。另请注意,obj.__nonzero__()
并不真正表示obj != 0
,而是bool(obj)
,这正是内置bool
在新样式类中所做的。
从技术上讲,列表(长度)和整数之间存在同构,但我认为没有理由让整个语言解决这个事实。【参考方案3】:
其他人已经注意到这是对传统(至少早于 C,也许更早)的逻辑扩展,即在布尔上下文中零为假,非零为真。其他人也已经注意到,在 Python 中,您可以通过自定义魔术方法等实现一些特殊效果。 (这确实直接回答了你的问题“它是否支持更多用例?”)其他人已经注意到,有经验的 Python 程序员会学习这个约定并习惯于这个约定,所以这对他们来说是非常明显和明确的。
但老实说,如果您不喜欢这种约定,而您只是为了自己的乐趣和利益而编程,那就不要遵循它。明确、清晰、和冗余比被混淆要好得多,,而且在冗余方面犯错几乎总是比混淆更好。
也就是说,归根结底,学习约定并不难,如果您作为小组项目的一部分进行编码,最好遵守该项目的约定,即使您不同意它们。
【讨论】:
【参考方案4】:隐式布尔多态是许多语言的一部分。其实成语
if msg:
print str(len(msg)) + ' char message: ' + msg
可能来自c!
void print_msg(char * msg)
if (msg)
printf("%d char message: %s", (int) strlen(msg), msg);
一个未分配的指针应该包含NULL
,它很方便地计算为假,从而使我们能够避免分段错误。
这是一个如此基本的概念,以至于它会阻碍 python 拒绝它。事实上,鉴于 c 中可用的布尔多态的相当笨拙的版本,很明显(至少对我而言)python 通过允许任何类型具有可定制的、以及- 通过__nonzero__
定义的布尔转换。
【讨论】:
我不会做太多的 C,但你确定你的语句正在检查 null 吗?我敢打赌它只是检查二进制表示是否非零。即:如果 null 在某些架构上为 0xdeadbeef,则此代码已损坏(或取决于特定环境的实现定义行为)。 即使是 Scala 的隐式强制转换也比这更有意义。这是一种一次性语言功能,可以在每个实例中保存 5-10 个字符。 Haskell 没有这些,并且很好地逃脱了,通常产生比 Python 更简洁和健壮的代码。如果没有这个功能,Ruby 也可以很好地离开 IIRC。这发生在 C 中,因为它是弱类型的(就像 php、Perl、JS、shell 等)。弱打字应该被普遍认为是有害的,因为它完全是任意的,你需要在脑海中保留一张 (originalType,func,morestuff) -> 强制类型的巨大地图。 在 Python 中一个令人惊讶的弱类型是 True == 1 和 False == 0,但这可能是出于向后或向前兼容性的原因。现在想来,这个成语很可能是因为向后兼容(Python 过去没有布尔值)。 @GNUnit,首先,不。见here;NULL
在 c 中总是错误的。其次,您的句子以“弱类型应该...”开头,提供了弱类型的单一缺点,作为声明普遍危害的理由。我认为这是一个糟糕的推理。当然,在所有条件相同的情况下,更强的类型会更好,但所有条件都很少是相同的。第三,Python 的类型系统比 c 的要强大得多,因为每个变量都有一个类型,不像 c,而且每个合法转换的效果都是显式定义的,不像 c。
@GNUnit,更一般地说,duck 类型不是弱类型。甚至 Haskell 也支持多态性,所以要说“Haskell 没有这个”,你真的必须定义你认为“这个”是什么。【参考方案5】:
if c:
# ...
是明确的。 "if" 明确指出后面的表达式将在布尔上下文中进行计算。
这样说是公平的
if c == 0:
# ...
更清晰,编程新手可能会同意。
但是对于一个有经验的程序员(至少这个)来说,后者并不清晰,它是多余的。由于“如果”,我已经知道 c 将与 0 进行比较;我不需要被告知两次。
【讨论】:
显式不是绝对的事情,但我很确定大多数人会同意在某些情况下将某些东西强制为布尔值是隐式的。当然,做明确的事情可能是多余的,这是对的;那是因为整个语言都是围绕这个习语设计的。在现实世界的情况下,显式情况根本不是多余的,实际上节省了时间,因为人们不必去看看 x 对象如何强制转换为布尔值。更不用说有不同的方法可以完成 (__not__, __len__, __nonzero__, etc
) 复杂性堆积。
@GNUnit:同意,强制机制是隐式的。但是“c == 0”并没有使它更清楚。 "len(c) == 0" 值得论证:不那么简短,但更明确。
c
在这里是一个整数,除非我遗漏了什么......
我实际上喜欢与0
进行比较。对于列表,简单的语法使其更清晰,但我不喜欢这种数字语法。但可能只有我一个人。【参考方案6】:
评估为 False 的空列表和评估为 True 的非空列表是 Python 的核心约定,它足够明确。如果 if 语句的上下文甚至传达了 c
是一个列表这一事实,len(c) == 0
就不会更明确了。
我认为 len(c) == 0
仅在您真的想检查长度而不是魔术 zeroness 或 emptiness 时更明确,因为它们的评估可能与长度检查不同.
【讨论】:
len(c) == 0
对于复杂对象是显式的,因为复杂对象可能有不同的长度概念,例如,在 django 中,QuerySet
、len()
导致整个查询运行并被转储到内存,然后计数,而 .count()
在 SQL 中执行 COUNT()
查询。如果不进行调查,您将无法知道 not some_query_set
的作用,因此您的代码的读者现在必须进行额外的研究,因为您懒得输入 if len(queryset) == 0
。
那么我想成语应该是“真实”做任何__len__
做的事情,但正如你所看到的,这里的复杂性开始堆积起来。
至于QuerySet
s,它更像是集合的接口而不是实际的集合,我同意not queryset
不是很明确。文档建议使用queryset.exists()
来检查queryset
提供的集合是否为空 - 所以这里的表达式是最明确的。但是,只要某个类的文档清楚地传达了如何在布尔上下文中评估实例,我认为 not someobject
就足够明确了。
没错,您必须四处阅读文档以了解是否可以使用一些本应为您节省 2.5 秒的习语。在很多情况下,人们甚至没有记录您是否可以使用该成语。您永远无法知道您的代码是否正确的另一个原因。
对于常规的 Python 类型,这个成语非常完美,每个人(应该)都知道它的意思。在标准 Python 之外,您不知道对象在魔术方法上的行为方式,您不应该使用它们或阅读文档 - 很容易,不是吗?任何具有一定复杂程度的代码都不会如此明确以至于您不需要任何文档。【参考方案7】:
“简单胜于复杂。”
“可读性很重要。”
以if users
为例——它比if len(users) == 0
更具可读性
【讨论】:
呃,“if 语句”存在的全部原因是如果语句的参数为 True,则执行一段代码。所以这个答案与我这里所说的无关。 大声笑哦,你编辑了你的答案 @GNUnit 是的,抱歉,这有点愚蠢>if users
对我来说看起来很模棱两可。如果您了解 Python,您应该知道什么是列表,并且所有列表都具有长度功能。如果您不了解 Python,我会说 if users
的可读性较差,因为它不一定传达有关 users
的问题的任何信息。
@GNUnit:任何语言的可读性都会降低,除非你学会了它的习语。【参考方案8】:
可能被第一项和第三项胜过:
美胜于丑。 简单胜于复杂。【讨论】:
那么你是在暗示这更简单吗?这怎么简单? @GNUnit,表达式更简单——组件更少。 按照这个逻辑,Perl 比 Python 简单。 ...但是更丑。你必须把禅作为一个整体,而不是一个点。 @krupan:所以这是安全和审美价值之间的权衡?以上是关于为啥要在 Python 中隐式检查是不是为空? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章
C++ 为啥基类/结构构造函数不能有多个参数可以从派生中隐式调用?
`with canvas:` (Python `with something() as x:`) 如何在 Kivy 中隐式工作?