【问题标题】:x or y: acceptable idiom, or obfuscation?x 或 y:可接受的习语,还是混淆?
【发布时间】:2011-04-06 00:08:30
【问题描述】:

我必须从可能为 None 的变量中提取值,并考虑一些默认值。我先写了这段代码:

if self.maxTiles is None:
    maxX, maxY = 2, 2
else:
    maxX, maxY = self.maxTiles

然后我意识到我可以将其缩短为:

maxX, maxY = self.maxTiles if self.maxTiles is not None else (2, 2)

但后来我意识到这可能是最简洁易读的:

maxX, maxY = self.maxTiles or (2, 2)

后者是可以接受的,还是太老套了?

【问题讨论】:

  • 最易读的版本是第一个。
  • 打代码高尔夫没有赢家。
  • @Andrei,第一个版本在两个分支之间存在令人不快的、任意的、无端的不对称,只需将 if 分支的主体变为 maxX, maxY = 2, 2 即可轻松避免。
  • @Alex:好点子!现在我将其更改为镜像 else 分支,它看起来更具吸引力

标签: python coding-style obfuscation idioms


【解决方案1】:

关于,具体来说,

self.maxTiles if self.maxTiles is not None else (2, 2)

我发现一般形式的“双重否定”if not A: B else: C(无论是作为语句还是表达式)可能会非常令人困惑/误导;这不是字面上的if not .. else,但移动not 不会使“双重否定”消失。

所以,一般来说,我只是将这样的构造重写为if A: C else: B。在这种特殊情况下,如果我确实选择了三元运算符形式,我会将其编码为

(2, 2) if self.maxTiles is None else self.maxTiles

关于更一般的问题:a = b or c 很好 当且仅当你真的想使用 c 来表示 任何 b 的错误值 - - 可以专门处理bNone。 IOW,b or c 是更好的表达方式

b if b else c

但它不是一种表达类似表达的方式,其中核心测试是b is None。理论上,如果您“知道”b 唯一可能的错误值是None,它们在语义上是等价的,但是对于代码的读者/维护者来说,这种强“仅可能的错误值”约束将不明显- 如果您必须添加注释来解释这一点,or 可能声称的任何简洁优势都将无效......如果可行,最好“用代码说出来”,而不是让代码晦涩难懂和需要cmets 来明确它在做什么和何时(真正有用的 cmets 是那些解释,而不是解释什么和何时 [[代码本身应该显示这一点!-)]],而是 为什么 当它不明显时——这个特定的代码功能的应用程序目的是什么)。

【讨论】:

  • +1 - 让我们将其推广为默认参数的 Python 状态成语:self.var = default_value if arg is None else arg。最少的重复,清楚地表明传递“无”意味着“使用默认值”,并且不会对混淆聪明和/或表达式的值进行意外布尔评估的风险。
  • 我们应该停止避免不再使用双重否定!
  • @Paul,写上你的第一条评论,如果None 绝对是已知的不是一个可接受的值,那很好。否则,_sentinel = object()def 之前并且通常在同一范围内,在def 中使用(用arg=_sentinel 代替arg=None),然后使用if arg is _sentinel 代替is None,是更合理的方法。除此之外,对于不需要避免三元运算的代码(在旧的 Python 版本上运行,或者非常面向初学者),这很好(否则,我建议多语句方法)。
  • @Paul:这样做的主要目的是,如果通常情况下不使用默认值,这很奇怪,因为它一直在右边
  • @Claudiu,纯功能(或几乎如此)编程语言中的最佳习语,与以 OOP 为核心的多范式语言相比,显然截然不同,以至于它会以对方的标准来判断是荒谬的。在完全围绕此类结构设计的语言中,名称重新绑定显然是一个非常好的习语,就像(比如说)尾递归在旨在最佳支持该结构的语言中非常棒。当您将名称重新绑定判断为“可能导致问题的突变”在 Python 中时,您只是应用了不适当的标准。
【解决方案2】:

连同 gddc 的答案(假设 maxTiles 是一个元组的问题),我可能会做第二个选项,但为清楚起见添加括号:

maxX, maxY = (self.maxTiles) if (self.maxTiles is not None) else (2, 2)

【讨论】:

  • 我更喜欢这个。这是一种表达默认值的自然方式。
【解决方案3】:

如果您在beginning of a function 这样做,我会使用较长的形式,因为它更惯用且易于识别。是的,它是更多的行,但你几乎没有保存任何字符,并且适合 79 个字符行的短行 = 好。

另外,如果您不得不调整逻辑或添加更多步骤,无论如何您都可能会恢复为长格式。

【讨论】:

  • +1 用于考虑可维护性。维护代码的人很可能不是作者!
【解决方案4】:

我尽可能避免使用y if x else z 语法。它本质上是一种丑陋、不直观的语法,也是 Python 设计中最大的错误之一。这是一个无序表达式:x 在 y 之前计算。这是不直观的;它自然读作“如果 x 则 y,否则 z”。 C 的语法为我们提供了几十年来建立的、普遍理解的顺序:x? y:z。 Python 搞错了。

也就是说,三元语法无论如何都是提供默认值的错误机制。在self.maxTiles if self.maxTiles is not None else (2, 2) 中,请注意冗余:您必须指定self.maxTiles 两次。这是重复的,所以阅读代码需要更多的工作。我必须阅读它两次以确保它没有说,例如,self.minTiles if self.maxTiles is not None else (2, 2)

self.maxTiles or (0,2) 避免了这些问题;一目了然。

一个警告:如果 self.maxTiles 是 ()0 或其他一些错误值,则结果会有所不同。根据您似乎在做什么,这可能是可以接受的,但请记住这一点。当为布尔值或整数提供默认值时,这是一个问题,您确实需要is None 测试。对于那些我更喜欢简单的条件,但有时会使用三元表达式。

编辑;编写条件版本的更清晰的方法是:

if self.maxTiles is None:
    maxX, maxY = 2, 2
else:
    maxX, maxY = self.maxTiles

【讨论】:

    【解决方案5】:

    您的代码是完全可以接受的习语。事实上,我发现它比前两个更具可读性。

    我唯一的考虑是你在一行中做两件事,提供一个默认值并将它们解压缩到 x,y 中。如果将它们分成两部分,可能会更清楚。

    maxTiles = self.maxTiles or (2, 2)
    maxX, maxY = maxTiles
    

    这也转移了对 g.d.d.c 的批评,虽然它不是一个真正的严重的批评。

    【讨论】:

      【解决方案6】:

      我不喜欢在 Python 中使用 orand 作为三元运算符的替代品。当我只想检查None 时,我遇到了很多问题,例如0 值被视为“假”太多次。我认为最好是明确的,即使它更冗长,所以你的第二个例子是最好的:

      maxX, maxY = self.maxTiles if self.maxTiles is not None else (2, 2)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-04-05
        • 1970-01-01
        • 1970-01-01
        • 2013-02-12
        • 1970-01-01
        • 2013-08-25
        相关资源
        最近更新 更多