why UnboundLocalError?

A

Alex Gittens

I'm trying to define a function that prints fields of given widths
with specified alignments; to do so, I wrote some helper functions
nested inside of the print function itself. I'm getting an
UnboundLocalError, and after reading the Naming and binding section in
the Python docs, I don't see why.

Here's the error:
fieldprint([5, 4], 'rl', ['Ae', 'Lau'])
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "fieldprint.py", line 35, in fieldprint
str += cutbits()
File "fieldprint.py", line 11, in cutbits
for i in range(0, len(fields)):
UnboundLocalError: local variable 'fields' referenced before assignment

This is the code:
def fieldprint(widths,align,fields):

def measure():
totallen = 0
for i in range(0, len(fields)):
totallen += len(fields)
return totallen

def cutbits():
cutbit = []
for i in range(0, len(fields)):
if len(fields) >= widths:
cutbit.append(fields[:widths])
fields = fields[widths:]
elif len(fields) > 0:
leftover = widths - len(fields)
if align == 'r':
cutbit.append(' '*leftover + fields)
elif align == 'l':
cutbit.append(fields + ' '*leftover)
else:
raise 'Unsupported alignment option'
fields = ''
else:
cutbit.append(' '*widths)
return cutbit.join('')

if len(widths) != len(fields) or len(widths)!=len(align) or
len(align)!=len(fields):
raise 'Argument mismatch'

str = ''


while measure()!=0:
str += cutbits()

What's causing the error?

Thanks,
Alex
 
P

Peter Hansen

Alex said:
I'm trying to define a function that prints fields of given widths
with specified alignments; to do so, I wrote some helper functions
nested inside of the print function itself. I'm getting an
UnboundLocalError, and after reading the Naming and binding section in
the Python docs, I don't see why.

Here's the error:
fieldprint([5, 4], 'rl', ['Ae', 'Lau'])

Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "fieldprint.py", line 35, in fieldprint
str += cutbits()
File "fieldprint.py", line 11, in cutbits
for i in range(0, len(fields)):
UnboundLocalError: local variable 'fields' referenced before assignment

This is the code:
def fieldprint(widths,align,fields):
[snip]
def cutbits():
cutbit = []
for i in range(0, len(fields)):
if len(fields) >= widths:
cutbit.append(fields[:widths])
fields = fields[widths:]

What's causing the error?


While I don't know the "correct" fix, the immediate cause is that you
are assigning to "fields" in the final line above, and there is no
"global fields" statement, so the compiler thinks it must be a local,
but you access it (as the error says) before it is assigned-to locally.

"A" fix would be to use a different name locally, and if you really want
it to reference the externally defined "fields", just do something like
"lfields = fields" at the top of cutbits() (where "lfields" means
"local fields", though you can pick any name you like).

-Peter
 
S

Steven D'Aprano

str = ''
while measure()!=0:
str += cutbits()

It is considered poor practice to use the names of built-ins like str
or list as variable names.
What's causing the error?

Did you actually copy all of the code? It seems to me that once your code
has assembled the string, it doesn't actually do anything with it. Not
even return it.

Also, you seem to be calling a join method on lists. Lists don't have join
methods.

The rest of your code is very hard for me to follow, especially as you
have not put any comments in and seem to spend a lot of time re-inventing
the wheel. I would do something like this to handle fixed-width printing.

(Untested.)

def fieldprint(widths,alignments,fields):
"""Given a list of widths, alignments and fields (text) representing
a single row of text formatted in columns, returns an aligned version.
Text is never truncated to fit, and is padded with spaces.

Examples:
fieldprint([5, 5, 5], "lcr", ["cat", "12345", "12345"])
=> "cat 1234512345"
fieldprint([5, 5, 5], "rcl", ["cat", "dog", "12345"])
=> " cat dog 12345"
fieldprint([5, 5, 5], "rcl", ["cat", "dog", "mice"])
=> " cat dog mice "

"""

# helper function
def align(width, alignment, text):
"""Give a width, an alignment, and some text, returns a string
containing that text aligned correctly in columns.
"""
if alignment in "Ll":
# left-justify
return text.ljust(width)
elif alignment in "Rr":
# right-justify
return text.rjust(width)
elif alignment in "Cc":
# centered
return text.center(width)
else:
raise ValueError("Unsupported alignment '%s'" % alignment)

if not len(widths) == len(alignments) == len(fields):
raise 'Argument mismatch'
columns = zip(widths, alignments, fields)
# builds a list of tuple of (width, alignment, field)
L = [] # hold each formatted column as it is built
for column in columns:
L.append(align(*column))
# equivalent to:
# L.append(align(column[0], column[1], column[2]))
return ''.join(L)


Hope this is helpful to you.
 
B

Bengt Richter

I'm trying to define a function that prints fields of given widths
with specified alignments; to do so, I wrote some helper functions
nested inside of the print function itself. I'm getting an
UnboundLocalError, and after reading the Naming and binding section in
the Python docs, I don't see why.

Here's the error:
fieldprint([5, 4], 'rl', ['Ae', 'Lau'])
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "fieldprint.py", line 35, in fieldprint
str +=3D cutbits()
File "fieldprint.py", line 11, in cutbits
for i in range(0, len(fields)):
UnboundLocalError: local variable 'fields' referenced before assignment

This is the code:
def fieldprint(widths,align,fields):

def measure():
totallen =3D 0
for i in range(0, len(fields)):
totallen +=3D len(fields)
return totallen

def cutbits():
cutbit =3D []
for i in range(0, len(fields)):
if len(fields) >=3D widths:
cutbit.append(fields[:widths])
fields =3D fields[widths:]

fields = fields[widths:] # did you mean this, analogous to [1] below?
elif len(fields) > 0:
leftover =3D widths - len(fields)
if align =3D=3D 'r':
cutbit.append(' '*leftover + fields=
)
elif align =3D=3D 'l':
cutbit.append(fields + ' '*lefto=
ver)
else:
raise 'Unsupported alignment option=
'
fields =3D ''

^^^^^^^^^-- [1]
else:
cutbit.append(' '*widths)
return cutbit.join('')

if len(widths) !=3D len(fields) or len(widths)!=3Dlen(align) or
len(align)!=3Dlen(fields):
raise 'Argument mismatch'

str =3D ''


while measure()!=3D0:
str +=3D cutbits()

What's causing the error?

Maybe the missing 's ?

It's not clear what you are trying to do with a field string that's
wider than the specified width. Do you want to keep printing lines that
have all blank fields except for where there is left-over too-wide remnants? E.g.,
if the fields were ['left','left12345','right','12345right'] and the widths were [5,5,6,6] and align 'llrr'

should the printout be (first two lines below just for ruler reference)
1234512345123456123456
LLLLLLLLLLRRRRRRRRRRRR
left left1 right12345r
2345 ight

or what? I think you can get the above with more concise code :)
but a minor mod to yours seems to do it:
... def measure():
... totallen = 0
... for i in range(0, len(fields)):
... totallen += len(fields)
... return totallen
... def cutbits():
... cutbit = []
... for i in range(0, len(fields)):
... if len(fields) >= widths:
... cutbit.append(fields[:widths])
... #fields = fields[widths:]
... fields = fields[widths:] # did you mean this, analogous to [1] below?
... elif len(fields) > 0:
... leftover = widths - len(fields)
... if align == 'r':
... cutbit.append(' '*leftover + fields)
... elif align == 'l':
... cutbit.append(fields + ' '*leftover)
... else:
... raise 'Unsupported alignment option'
... fields = '' # <-- [1]
... else:
... cutbit.append(' '*widths)
... # XXX return cutbit.join('')
... return ''.join(cutbit)
... if len(widths) != len(fields) or len(widths)!=len(align) or len(align)!=len(fields):
... raise 'Argument mismatch'
... # str = ''
... result_lines = []
... while measure()!=0:
... result_lines.append(cutbits())
... #str += cutbits()
... return '\n'.join(result_lines)
...
>>> fieldprint([5,5,6,6], 'llrr', ['left', 'left12345', 'right', '12345right']) 'left left1 right12345r\n 2345 ight'
>>> print fieldprint([5,5,6,6], 'llrr', ['left', 'left12345', 'right', '12345right'])
left left1 right12345r
2345 ight

Note that
for i in xrange(len(items)):
item = items
# mess with item
just to walk through items one item at a time is more concisely written
for item in items:
# mess with item
and if you really need the index i as well,
for i, item in enumerate(items):
# use item and i here

Also, if you are going to raise 'Argument mismatch' it would be better to do it at the beginning
and use a standard exception. String exceptions are frowned upon these days, so probably raise
ValueError or TypeError with the string as an argument instead, unless you have a more informative thing to do.
BTW, measure above can be defined with current builtins as (untested)
def measure(): return sum([len(field) for field in fields])
I'll leave a few things ...

HTH

Regards,
Bengt Richter
 
A

and-google

Alex said:
I'm getting an UnboundLocalError
def fieldprint(widths,align,fields): [...]
def cutbits(): [...]
fields = fields[widths:]


There's your problem. You are assigning 'fields' a completely new
value. Python doesn't allow you to rebind a variable from an outer
scope in an inner scope (except for the special case where you
explicitly use the 'global' directive, which is no use for the nested
scopes you are using here).

So when you assign an identifier in a function Python assumes that you
want that identifier to be a completely new local variable, *not* a
reference to the variable in the outer scope. By writing 'fields= ...'
in cutbits you are telling Python that fields is now a local variable
to cutbits. So when the function is entered, fields is a new variable
with no value yet, and when you first try to read it without writing to
it first you'll get an error.

What you probably want to do is keep 'fields' pointing to the same
list, but just change the contents of the list. So replace the assign
operation with a slicing one:

del fields[:widths]
 
B

Bengt Richter

Alex said:
I'm getting an UnboundLocalError
def fieldprint(widths,align,fields): [...]
def cutbits(): [...]
fields = fields[widths:]


There's your problem. You are assigning 'fields' a completely new
value. Python doesn't allow you to rebind a variable from an outer
scope in an inner scope (except for the special case where you
explicitly use the 'global' directive, which is no use for the nested
scopes you are using here).

So when you assign an identifier in a function Python assumes that you
want that identifier to be a completely new local variable, *not* a
reference to the variable in the outer scope. By writing 'fields= ...'
in cutbits you are telling Python that fields is now a local variable
to cutbits. So when the function is entered, fields is a new variable
with no value yet, and when you first try to read it without writing to
it first you'll get an error.

What you probably want to do is keep 'fields' pointing to the same
list, but just change the contents of the list. So replace the assign
operation with a slicing one:

del fields[:widths]


Except the OP probably had two errors in that line, and doesn't want to slice
out fields from the field list, but rather slice characters from the i-th field,
and strings are immutable, so he can't do
del fields[:widths] # slice deletion illegal if fields is a string
and so
fields = fields[:widths]
would be the way to go (see my other post).

Regards,
Bengt Richter
 
B

Bengt Richter

I'm trying to define a function that prints fields of given widths
with specified alignments; to do so, I wrote some helper functions
nested inside of the print function itself. I'm getting an
UnboundLocalError, and after reading the Naming and binding section in
the Python docs, I don't see why.
It's not clear what you are trying to do with a field string that's
wider than the specified width. Do you want to keep printing lines that
have all blank fields except for where there is left-over too-wide remnants? E.g.,
if the fields were ['left','left12345','right','12345right'] and the widths were [5,5,6,6] and align 'llrr'

should the printout be (first two lines below just for ruler reference)
1234512345123456123456
LLLLLLLLLLRRRRRRRRRRRR
left left1 right12345r
2345 ight

or what? I think you can get the above with more concise code :)
but a minor mod to yours seems to do it:
[...]

Not very tested, but you may enjoy puzzling out a more concise version
(and writing a proper test for it, since it might have bugs ;-)
... if len(widths) != len(fields) or len(widths)!=len(align):
... raise ValueError, 'fieldprint argument mismatch'
... align = [getattr(str, j+'just') for j in align]
... results = []
... while ''.join(fields):
... for i, (fld, wid, alg) in enumerate(zip(fields, widths, align)):
... results.append(alg(fld[:wid], wid))
... fields = fld[wid:]
... results.append('\n')
... return ''.join(results)
...
>>> print fieldprint([5,5,6,6], 'llrr', ['left', 'left12345', 'right', '12345right'])
left left1 right12345r
2345 ight

BTW, since this destroys the fields list, you might want to operate on a copy
(e.g., remaining_fields = fields[:]) internally, or pass a copy to the routine.
Of course, you could put "print" instead of "return" in fieldprint and just call it
instead of printing what it returns as above, but you didn't show that part in your
original example. BTW2, note that "str" above is the built in string type, and it's
not a good idea to use a built in name for a variable the way your original code did.

Regards,
Bengt Richter
 

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,764
Messages
2,569,566
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top