pythonvariablesscope

Should I avoid initialization of variables inside blocks?


I have a code like this

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

Initialization of a inside of the block looks bad for me. Can it be written better?

I cannot use ternary operator, because names of functions and/or lists of arguments are long. Saying "long" I mean that the following expression

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

will take more than 79 (sometimes even more than 119) symbols with real names instead of a, f, g, x, y and condition. Usage of multiple slashes will make the code ugly and messy.

I don't want to initialize a with result of one of the functions by defaul, because both function are slow and I cannot allow such overhead

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

I can initialize the variable with None, but is this solution pretty enough?

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

Let me explain my position: in C and C++ variables inside of a block have the block as their scope. In ES6 the let keyword was introduced — it allows to create variables with the same scoping rules as variables in C and C++. Variables defined with old var keyword have similar scoping rules as in Python. That's why I think that initialization of variables should be made outside blocks if I want to use the variables outside these blocks.

Here is more complicated example:

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)

I go through elements of some generator gen, process them and perform some actions on the result on each iteration. Then I want to do something with the last result outside of the loop.

Is it pythonic enough to not assign a dummy value to the result beforehand? Doesn't this make the code less readable?

Is it good to initialize variables inside if/else/for/etc. in Python?


Solution

  • Ok, there are two points that need to be clarified here which are fundamental to python.

    1. There is no variable declaration/initialization in python. An expression like a = f(x) is simply a scheme to name the object that is returned by f as a. That namea can be later used to name any other object no matter what its type is. See this answer.

    2. A block in python is either the body of a module, a class or a function. Anything defined/named inside these objects are visible to later code until the end of a block. A loop or an if-else is not a block. So any name defined before outside the loop or if/else will be visible inside and vice versa. See this. global and nonlocal objects are a little different. There is no let in python since that is default behavior.

    In your case the only concern is how you are using a further in the code. If you code expects the type of objects returned by f or g it should work fine unless there is an error. Because at least one of the if or the else should run in a normal operation so a will refer to some kind of an object (if the names were different in if and else that would be a problem). If you want to make sure that the subsequent code does not break you can use a try-except-else to catch any error generated by the functions and assign a default value to a in the except clause after appropriate reporting/logging of the error.

    Hence to summarize and also to directly address your question, assigning names to objects inside an if-else statement or a loop is perfectly good practice provided:

    1. The same name is used in both if and else clause so that the name is guaranteed to refer to an object at the end of the statement. Additional try-except-else error catching can take care of exceptions raised by the functions.

    2. The names should not be too short, generic or something that does not make the intention of the code clear like a, res etc. A sensible name will lead to much better readability and prevent accidental use of the same name later for some other object thereby losing the original.