Variable scoping rules in Python?

J

joshua.davies

Ok, I'm relatively new to Python (coming from C, C++ and Java). I'm
working on a program that outputs text that may be arbitrarily long,
but should still line up, so I want to split the output on a specific
column boundary. Since I might want to change the length of a column,
I tried defining the column as a constant (what I would have made a
"#define" in C, or a "static final" in Java). I defined this at the
top level (not within a def), and I reference it inside a function.
Like this:

COLUMNS = 80

def doSomethindAndOutputIt( ):
...
for i in range( 0, ( len( output[0] ) / COLUMNS ) ):
print output[0][ i * COLUMNS : i * COLUMNS + ( COLUMNS - 1 ) ]
print output[1][ i * COLUMNS : i * COLUMNS + ( COLUMNS - 1 ) ]
..

etc. etc. It works fine, and splits the output on the 80-column
boundary just like I want.

Well, I decided that I wanted "COLUMNS = 0" to be a special "don't
split anywhere" value, so I changed it to look like this:

COLUMNS = 80

def doSomethindAndOutputIt( ):
...
if COLUMNS == 0:
COLUMNS = len( output[ 0 ] )

for i in range( 0, ( len( output[0] ) / COLUMNS ) ):
print output[0][ i * COLUMNS : i * COLUMNS + ( COLUMNS - 1 ) ]
print output[1][ i * COLUMNS : i * COLUMNS + ( COLUMNS - 1 ) ]
..

Now, when I run it, I get the following error:

Traceback (most recent call last):
File "Test.py", line 140, in ?
doSomethingAndOutput( input )
File "Test.py", line 123, in doSomethingAndOutput
if COLUMNS == 0:
UnboundLocalError: local variable 'COLUMNS' referenced before
assignment

I went back and re-read chapter 13 of "Learning Python", which talks
about variable scoping rules, and I can't figure out why Python is
saying this variable in Unbound. It works if I insert:

global COLUMNS

before the "if" statement... but I don't understand why. Is the
interpreter scanning my entire function definition before executing
it, recognizing that I *might* assign COLUMNS to a value, and deciding
that it's a local on that basis?
 
D

Diez B. Roggisch

Ok, I'm relatively new to Python (coming from C, C++ and Java). I'm
working on a program that outputs text that may be arbitrarily long,
but should still line up, so I want to split the output on a specific
column boundary. Since I might want to change the length of a column,
I tried defining the column as a constant (what I would have made a
"#define" in C, or a "static final" in Java). I defined this at the
top level (not within a def), and I reference it inside a function.
Like this:

COLUMNS = 80

def doSomethindAndOutputIt( ):
...
for i in range( 0, ( len( output[0] ) / COLUMNS ) ):
print output[0][ i * COLUMNS : i * COLUMNS + ( COLUMNS - 1 ) ]
print output[1][ i * COLUMNS : i * COLUMNS + ( COLUMNS - 1 ) ]
..

etc. etc. It works fine, and splits the output on the 80-column
boundary just like I want.

Well, I decided that I wanted "COLUMNS = 0" to be a special "don't
split anywhere" value, so I changed it to look like this:

COLUMNS = 80

def doSomethindAndOutputIt( ):
...
if COLUMNS == 0:
COLUMNS = len( output[ 0 ] )

for i in range( 0, ( len( output[0] ) / COLUMNS ) ):
print output[0][ i * COLUMNS : i * COLUMNS + ( COLUMNS - 1 ) ]
print output[1][ i * COLUMNS : i * COLUMNS + ( COLUMNS - 1 ) ]
..

Now, when I run it, I get the following error:

Traceback (most recent call last):
File "Test.py", line 140, in ?
doSomethingAndOutput( input )
File "Test.py", line 123, in doSomethingAndOutput
if COLUMNS == 0:
UnboundLocalError: local variable 'COLUMNS' referenced before
assignment

I went back and re-read chapter 13 of "Learning Python", which talks
about variable scoping rules, and I can't figure out why Python is
saying this variable in Unbound. It works if I insert:

global COLUMNS

before the "if" statement... but I don't understand why. Is the
interpreter scanning my entire function definition before executing
it, recognizing that I *might* assign COLUMNS to a value, and deciding
that it's a local on that basis?

Yep. That's essentially it. Because python has no explicit variable
declaration, it looks on the left hand side of assignments to determine the
locals.

Diez
 
B

Bruno Desthuilliers

(e-mail address removed) a écrit :
Ok, I'm relatively new to Python (coming from C, C++ and Java). I'm
working on a program that outputs text that may be arbitrarily long,
but should still line up, so I want to split the output on a specific
column boundary.

FWIW :
http://docs.python.org/lib/module-textwrap.html

Since I might want to change the length of a column,
I tried defining the column as a constant (what I would have made a
"#define" in C, or a "static final" in Java). I defined this at the
top level (not within a def), and I reference it inside a function.
Like this:

COLUMNS = 80

def doSomethindAndOutputIt( ):
...
for i in range( 0, ( len( output[0] ) / COLUMNS ) ):
print output[0][ i * COLUMNS : i * COLUMNS + ( COLUMNS - 1 ) ]
print output[1][ i * COLUMNS : i * COLUMNS + ( COLUMNS - 1 ) ]
..

etc. etc. It works fine, and splits the output on the 80-column
boundary just like I want.

Well, I decided that I wanted "COLUMNS = 0" to be a special "don't
split anywhere" value, so I changed it to look like this:

COLUMNS = 80

def doSomethindAndOutputIt( ):
...
if COLUMNS == 0:
COLUMNS = len( output[ 0 ] )
>
for i in range( 0, ( len( output[0] ) / COLUMNS ) ):
print output[0][ i * COLUMNS : i * COLUMNS + ( COLUMNS - 1 ) ]
print output[1][ i * COLUMNS : i * COLUMNS + ( COLUMNS - 1 ) ]
..

Since you don't want to modify a global (and even worse, a CONSTANT),
the following code may be a bit cleaner:

def doSomethindAndOutputIt( ):
...
if COLUMNS == 0:
columns = len( output[ 0 ] )
else:
columns = COLUMNS
for i in range( 0, ( len( output[0] ) / COLUMNS ) ):
print output[0][ i * columns : i * columns + ( columns - 1 ) ]
..

Now, when I run it, I get the following error:

Traceback (most recent call last):
File "Test.py", line 140, in ?
doSomethingAndOutput( input )
File "Test.py", line 123, in doSomethingAndOutput
if COLUMNS == 0:
UnboundLocalError: local variable 'COLUMNS' referenced before
assignment

I went back and re-read chapter 13 of "Learning Python", which talks
about variable scoping rules, and I can't figure out why Python is
saying this variable in Unbound. It works if I insert:

global COLUMNS

before the "if" statement... but I don't understand why. Is the
interpreter scanning my entire function definition before executing
it, recognizing that I *might* assign COLUMNS to a value, and deciding
that it's a local on that basis?

You name it. That's *exactly* what happens.
 
P

Peter Otten

joshua.davies said:
Ok, I'm relatively new to Python (coming from C, C++ and Java). I'm
working on a program that outputs text that may be arbitrarily long,
but should still line up, so I want to split the output on a specific
column boundary. Since I might want to change the length of a column,
I tried defining the column as a constant (what I would have made a
"#define" in C, or a "static final" in Java). I defined this at the
top level (not within a def), and I reference it inside a function.
Like this:

COLUMNS = 80

def doSomethindAndOutputIt( ):
...
for i in range( 0, ( len( output[0] ) / COLUMNS ) ):
print output[0][ i * COLUMNS : i * COLUMNS + ( COLUMNS - 1 ) ]
print output[1][ i * COLUMNS : i * COLUMNS + ( COLUMNS - 1 ) ]
..

etc. etc. It works fine, and splits the output on the 80-column
boundary just like I want.

Just in case it's not intentional: You'll lose every 80th character as
python intervals do not include the upper bound. The same problem
affects the for loop -- e. g. when output[0] has less than COLUMNS
columns nothing is printed:
[]

Peter
 
S

Steven D'Aprano

I went back and re-read chapter 13 of "Learning Python", which talks
about variable scoping rules, and I can't figure out why Python is
saying this variable in Unbound. It works if I insert:

global COLUMNS

before the "if" statement... but I don't understand why. Is the
interpreter scanning my entire function definition before executing it,
recognizing that I *might* assign COLUMNS to a value, and deciding that
it's a local on that basis?

Almost right, but not quite.

You are right that Python decides that COLUMNS is local, but you're wrong
about when that decision gets made. Python does not re-scan your function
every time you execute it, but only once, when it is compiled.

Compiling the function might take one pass of the source code, or two, or
a thousand for all we know; that's an irrelevant detail of interest only
to compiler designers and the chronically curious. What's important is
that once the function is compiled, the decision is already made about
whether COLUMNS is local or not, and the virtual machine that executes
your function only needs to interpret the byte-code in a single pass.
 
P

Paul Hankin

Ok, I'm relatively new to Python (coming from C, C++ and Java). I'm
working on a program that outputs text that may be arbitrarily long,
but should still line up, so I want to split the output on a specific
column boundary. Since I might want to change the length of a column,
I tried defining the column as a constant (what I would have made a
"#define" in C, or a "static final" in Java). I defined this at the
top level (not within a def), and I reference it inside a function.
Like this:

COLUMNS = 80

def doSomethindAndOutputIt( ):
...
if COLUMNS == 0:
COLUMNS = len( output[ 0 ] )

Often it's better to make your global 'constant' an optional argument,
especially if certain values have special meanings to your function.

def doSomethingAndOutputIt(columns = 80):
...
columns = columns or len(output[0])
 
M

MRAB

(e-mail address removed) a écrit :
Ok, I'm relatively new to Python (coming from C, C++ and Java). I'm
working on a program that outputs text that may be arbitrarily long,
but should still line up, so I want to split the output on a specific
column boundary.

FWIW :http://docs.python.org/lib/module-textwrap.html




Since I might want to change the length of a column,
I tried defining the column as a constant (what I would have made a
"#define" in C, or a "static final" in Java). I defined this at the
top level (not within a def), and I reference it inside a function.
Like this:
COLUMNS = 80
def doSomethindAndOutputIt( ):
...
for i in range( 0, ( len( output[0] ) / COLUMNS ) ):
print output[0][ i * COLUMNS : i * COLUMNS + ( COLUMNS - 1 ) ]
print output[1][ i * COLUMNS : i * COLUMNS + ( COLUMNS - 1 ) ]
..
etc. etc. It works fine, and splits the output on the 80-column
boundary just like I want.
Well, I decided that I wanted "COLUMNS = 0" to be a special "don't
split anywhere" value, so I changed it to look like this:
COLUMNS = 80
def doSomethindAndOutputIt( ):
...
if COLUMNS == 0:
COLUMNS = len( output[ 0 ] )
for i in range( 0, ( len( output[0] ) / COLUMNS ) ):
print output[0][ i * COLUMNS : i * COLUMNS + ( COLUMNS - 1 ) ]
print output[1][ i * COLUMNS : i * COLUMNS + ( COLUMNS - 1 ) ]
..

Since you don't want to modify a global (and even worse, a CONSTANT),
the following code may be a bit cleaner:

def doSomethindAndOutputIt( ):
...
if COLUMNS == 0:
columns = len( output[ 0 ] )
else:
columns = COLUMNS
for i in range( 0, ( len( output[0] ) / COLUMNS ) ):
print output[0][ i * columns : i * columns + ( columns - 1 ) ]
..
[snip]
range() can accept 3 arguments (start, stop and step) so you could
write:

for i in range( 0, len( output[0] ), columns ):
print output[0][ i : i + columns ]
..
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,057
Latest member
KetoBeezACVGummies

Latest Threads

Top