The following Python program A outputs 1
, as expected, while the following Python program B raises an unbound local variable x
error, counterintuitively.
- Program A:
def f(): print(x)
x = 1
f()
- Program B:
def f(): print(x); x = 2
x = 1
f()
Javascript has the exact same behaviour.
- Program A:
function f() { console.log(x); }
let x = 1;
f();
- Program B:
function f() { console.log(x); let x = 2; }
let x = 1;
f();
However, C++ outputs 1
in both cases, as expected.
- Program A:
#include <iostream>
int x;
void f() { std::cout << x; }
int main() { x = 1; f(); return 0; }
- Program B:
#include <iostream>
int x;
void f() { std::cout << x; int x = 2; }
int main() { x = 1; f(); return 0; }
So all programs A output 1
. The differences in programs B between Python and Javascript on the one hand, and C++ on the other hand, result from their different scoping rules: in C++, the scope of a variable starts at its declaration, while in Python and Javascript, it starts at the beginning of the block where the variable is declared. Consequently, in C++ printing variable x
in function f
resolves to the value 1
of global variable x
since it is the only variable in context at this point of execution. In Python and Javascript printing variable x
in function f
resolves to nothing and raises an unbound local variable x
error since local variable x
is already in context at this point of execution and therefore it masks global variable x
without being bound yet to the value 2
. This counterintuitive behaviour of Python and Javascript is also known as variable hoisting since it ‘hoists’ variable declarations (but not definitions) at the beginning of their blocks.
Presumably variable hoisting interacts with other programming language design decisions, which leads to some languages choosing to use it and others not to. What are those interactions?
var
and function scope only), you could run "stupid" code without breaking the execution. The drawback was hard debuggable logic errors. – Sorosisvar
does not break execution likelet
does since it assigns the valueundefined
to local variablex
instead of nothing, but the scope of local variablex
remains the same since it still masks global variablex
likelet
does. – Cutelet
(andconst
), a termtemporal dead-zone
is used. – Sorosis