Python:我应该避免在块内部对变量进行初始化吗?

时间:2017-12-26 22:01:00

标签: python variables scope

问题

我有这样的代码

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做一些事情。

pythonic是否足以不事先为result分配虚拟值? 这不会使代码的可读性降低吗?

问题

初始化if / else / for /等内的变量是否合适?在Python?

3 个答案:

答案 0 :(得分:5)

Python没有块范围......范围是整个函数,它写得非常pythonic

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

如果你想用C ++编写,那么用C ++编写C ++,不要用Python编写C ++ ......这是一个坏主意。

答案 1 :(得分:2)

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

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

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

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

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

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

    2. 这些名称不应太短,一般或不能使代码意图明确的内容,如ares等。明智的名称会带来更好的可读性并防止以后意外使用同名的其他物品,从而丢失原件。

答案 2 :(得分:1)

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

#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。

我发现这很多,使用自定义网络单元测试代码 - 我从一个获取或发布到网址得到一个响应,我对响应进行了一系列测试。如果原始的get / post失败了,那么响应就不存在了,所以任何诊断如漂亮打印响应的属性都会引发异常,迫使你在异常处理程序有用之前清理它。

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