【问题标题】:Python: Should I avoid initialization of variables inside blocks?Python:我应该避免初始化块内的变量吗?
【发布时间】:2017-12-26 22:00:59
【问题描述】:

问题

我有这样的代码

if condition:
    a = f(x)
else:
    a = g(y)

块内部a 的初始化对我来说看起来很糟糕。能不能写得更好?

我不能使用三元运算符,因为函数名和/或参数列表很长。 说“长”是指下面的表达方式

a = f(x) if condition else g(y)

将采用超过 79 个(有时甚至超过 119 个)实名符号,而不是 afgxycondition。 使用多个斜线会使代码变得丑陋和混乱。

我不想用默认函数之一的结果初始化a,因为这两个函数都很慢,我不能允许这样的开销

a = g(y)
if condition:
    a = f(x)

我可以用None 初始化变量,但是这个解决方案够漂亮吗?

a = None
if condition:
    a = f(x)
else:
    a = g(y)

让我解释一下我的立场:在块内的 C 和 C++ 变量中,块作为它们的作用域。在 ES6 中引入了 let 关键字——它允许创建具有与 C 和 C++ 中的变量相同的作用域规则的变量。使用旧的 var 关键字定义的变量具有与 Python 中相似的范围规则。 这就是为什么我认为如果我想在这些块之外使用变量,应该在这些块之外进行变量的初始化。

更新

这里是更复杂的例子

for obj in gen:
    # do something with the `obj`
    if predicate(obj):
        try:
            result = f(obj)
        except Exception as e:
            log(e)
            continue
    else:
        result = g(obj)
    # do something useful with the `result`
else:
    result = h(obj)

display(result)

我检查了一些生成器gen 的元素,处理它们并在每次迭代时对result 执行一些操作。 然后我想对循环外的最后一个result 做点什么。

不预先为result 分配一个虚拟值是否足够pythonic? 这不会降低代码的可读性吗?

问题

if/else/for/etc里面初始化变量好不好?在 Python 中?

【问题讨论】:

  • 将内联 if-else 包裹在括号中,然后您可以在没有斜杠的情况下换行,并添加前导空格以提高可读性
  • Python 没有块作用域。您的 a = None 在语义上等同于将其省略...
  • 我倾向于使用外部 a = None。原因是,随着您的逻辑变得更加有条件和复杂,您可能会遇到分支未分配 a 的 NameError ,这会抛出您。设置 a 后,您可以针对 None 执行 getattr(a, 'somevar','default') 。在您的 简单 示例中,可能不值得。
  • @JLPeyret 是的,你明白我的意思:我说的是一般情况,但可能是因为术语错误,我被理解错了
  • @f5r5e5d 多行三元运算符根本不会比普通的if-else 更具可读性

标签: python variables scope


【解决方案1】:

Python 没有块作用域...作用域是整个函数,编写起来非常符合 Python 风格

if <condition>:
    a = f()
else:
    a = g()

如果你想用 C++ 编写,然后使用 C++ 编写 C++,不要使用 Python 编写 C++……这是个坏主意。

【讨论】:

    【解决方案2】:

    好的,这里有两点需要澄清,是python的基础。

    1. python 中没有变量声明/初始化。像a = f(x) 这样的表达式只是一个将 f 返回的对象命名为a 的方案。该名称a 以后可以用于命名任何其他对象,无论其类型是什么。请参阅this 答案。

    2. python 中的块可以是模块、类或函数的主体。在这些对象中定义/命名的任何内容对以后的代码都是可见的,直到块结束。循环或 if-else 不是块。因此,在循环之外或 if/else 之前定义的任何名称都将在内部可见,反之亦然。见thisglobalnonlocal 对象有点不同。 python 中没有let,因为这是默认行为。

    在您的情况下,唯一关心的是您如何在代码中进一步使用a。如果您的代码期望 fg 返回的对象类型,它应该可以正常工作,除非出现错误。因为 if 或 else 中至少有一个应该在正常操作中运行,所以 a 将引用某种对象(如果 if 和 else 中的名称不同,那将是一个问题)。如果您想确保后续代码不会中断,您可以使用 try-except-else 来捕获函数生成的任何错误,并在适当报告/记录错误后为 except 子句中的 a 分配默认值。

    因此总结并直接解决您的问题,在 if-else 语句或循环中为对象分配名称是非常好的做法:

    1. 在 if 和 else 子句中使用相同的名称,以保证该名称在语句末尾引用一个对象。额外的try-except-else 错误捕获可以处理函数引发的异常。

    2. 名称不应太短、太笼统或不能使代码意图清晰的名称,例如 ares 等。合理的名称将提高可读性并防止意外使用稍后为某些其他对象使用相同名称,从而丢失原始对象。

    【讨论】:

    • 感谢您的澄清!不过,我的问题是关于最佳实践的。我知道代码是正确的,我可以进一步使用a。重点是它是否足够可读,是否计划改变这种行为
    • 如果我正确理解了您原始问题的最后一段,您将 python 块与 C++ 和 JS 块混淆了,并且应该在块外部进行变量初始化以在外部使用它们。这就是为什么我澄清了为什么你不需要初始化任何东西以及块在 python 中的具体含义。 Python 没有像 let 这样的东西,因为这是默认行为,但在 python 块内使用 globals 使其在外部可见(但不推荐)。
    • 只要您对对象使用合理的名称(不要太短、通用或不能使意图足够清晰的名称,例如a),代码就应该是可读的,并且不太可能您将对其他对象使用相同的名称,从而丢失原始对象。因此,这是非常好的做法。
    • 请将此评论添加到您的答案中。它实际上回答了我的问题,并且对基础知识的澄清可以更好地理解这个问题
    【解决方案3】:

    让我澄清一下我在 cmets 中的意思。

    #this is not, strictly, needed, but it makes the 
    #exception handler more robust
    a = b = None
    
    try:
        if condition:
            a = f(x)
            b = v(x)
        else:
            a = g(y)
            b = v2(x)
    
        return w(a, b)
    
    except Exception, e:
        logger.exception("exception:%s" % (e))
        logger.exception("  the value of a was:%s" % (a))
        logger.exception("  the value of b was:%s" % (b))
        raise 
    

    这是非常标准的代码,您只想将整个内容封装在一些日志代码中,以防出现异常。我重新引发了原始异常,但也可以轻松返回默认值。

    问题是,除非异常等到return w(a, b) 发生,否则对ab 的任何访问都将根据那些尚未声明的变量抛出自己的NameError。

    这在我身上发生过很多次,使用自定义 Web 单元测试代码 - 我从获取或发布到 url 获得响应,并针对响应运行一堆测试。如果原始的 get/post 失败,则响应不存在,因此任何诊断(例如漂亮打印该响应的属性)都会引发异常,迫使您在异常处理程序有用之前进行清理。

    因此,为了防止这种情况,我将异常处理程序中引用的任何变量初始化为 None。当然,如果需要,你还得提防a变成None,比如logger("a.attr1:%s" % (getattr(a, "attr1","?")

    【讨论】:

    • 对于这种技术,我认为最好将初始化值设置为 None 以外的其他值。 None 在 python 中的许多其他情况下默认分配,例如当函数没有显式返回值时。从用户的角度来看,IMO 最好检查except 中是否存在ab(在vars() 或其他try-except 中)并显式生成一条消息,说明a/b 确实存在还没有值,因为一个或多个语句尚未执行。然后用户不必检查代码来查找初始值是什么。显式优于隐式。
    • Simpler is better than too complicated.
    猜你喜欢
    • 1970-01-01
    • 2012-02-25
    • 1970-01-01
    • 1970-01-01
    • 2012-02-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-15
    相关资源
    最近更新 更多