[Python-au] strange pre-acting bug
Ryan Kelly
ryan at rfk.id.au
Tue May 18 23:40:56 UTC 2010
On Wed, 2010-05-19 at 09:09 +1000, PeterL wrote:
> Interesting. This is a common mis-understanding on the scoping rules of
> Python functions. I might do a lightning talk on it at PyCon.
> You need to be clear on the error. An 'UnboundLocalError' means that you
> are using a mainline variable inside a function before you change it.
>
> In Python, a function *can* _read_ a mainline's variable with no problem.
> If a function goes to change a mainline's variable, then it become
> 'bound local', which means you must give it a value before you use it.
It might also be instructive to look at the bytecode that python
generates for each of these cases (or it might be that I just love
looking at bytecode...)
>
> Compare:
> -----------
> # Mainline variable available to a fn
> def fn():
> print x # Prints 5
>>> import dis
>>> dis.dis(fn)
2 0 LOAD_GLOBAL 0 (x)
3 PRINT_ITEM
4 PRINT_NEWLINE
5 LOAD_CONST 0 (None)
8 RETURN_VALUE
Since x is not given a value inside the function, it is accessed as a
global using the "LOAD GLOBAL" bytecode.
> ----------
> # Mainline variables can be localised inside a fn
> def fn():
> x = 7
> print x # Prints 7
> # Throw the localised value of x away
>>> dis.dis(fn)
2 0 LOAD_CONST 1 (7)
3 STORE_FAST 0 (x)
3 6 LOAD_FAST 0 (x)
9 PRINT_ITEM
10 PRINT_NEWLINE
11 LOAD_CONST 0 (None)
14 RETURN_VALUE
Since x is now given a value inside the function, it is treated as a
local variable and loaded using the "LOAD_FAST" bytecode. Note also the
"STORE_FAST" opcode which sets the value of the local variable.
This is an optimisation by the python interpreter. Accessing local
variables is actually a lot faster than accessing globals (basically,
it's an array lookup rather than a hash-table lookup).
> ----------
> # Local bound variables must be set before they're used
> def fn():
> print x # Error - UnboundLocalError - mainline variables that
> are localised must be set before used
> x = 7 # Localise x
> print x # Never reaches here
>>> dis.dis(fn)
2 0 LOAD_FAST 0 (x)
3 PRINT_ITEM
4 PRINT_NEWLINE
3 5 LOAD_CONST 1 (7)
8 STORE_FAST 0 (x)
4 11 LOAD_FAST 0 (x)
14 PRINT_ITEM
15 PRINT_NEWLINE
16 LOAD_CONST 0 (None)
19 RETURN_VALUE
Whoops! We now have a "LOAD_FAST" without a preceding "STORE_FAST",
which produces the UnboundLocalError you are seeing.
I've always considered this something of a "leaky optimisation" on the
part of the python interpreter, but once you know the rule it's quite
consistent and easy to avoid.
Basically, every variable has a single unambiguous scope and that scope
cannot change during function execution. Assigning to an otherwise
undeclared variable makes its scope local.
Cheers,
Ryan
--
Ryan Kelly
http://www.rfk.id.au | This message is digitally signed. Please visit
ryan at rfk.id.au | http://www.rfk.id.au/ramblings/gpg/ for details
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 197 bytes
Desc: This is a digitally signed message part
URL: <http://starship.python.net/pipermail/python-au/attachments/20100519/9bbb0f3e/attachment.pgp>
More information about the python-au
mailing list