This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Improve UnboundLocalError message for deleted names
Type: enhancement Stage: resolved
Components: Interpreter Core Versions: Python 3.7
process
Status: closed Resolution: duplicate
Dependencies: Superseder:
Assigned To: Nosy List: barry, levkivskyi, martin.panter, mbussonn, r.david.murray, rhettinger, serhiy.storchaka, veky
Priority: normal Keywords:

Created on 2017-02-17 17:06 by mbussonn, last changed 2022-04-11 14:58 by admin. This issue is now closed.

Pull Requests
URL Status Linked Edit
PR 141 closed mbussonn, 2017-02-17 17:06
Messages (9)
msg288023 - (view) Author: Matthias Bussonnier (mbussonn) * Date: 2017-02-17 17:06
Raymond Hettinger reported during PyCon Canada 2016 Keynote (~20m30 sec)
that unbound local error message was inaccurate, it state that :

> local variable 'xxx' referenced before assignment

Though it can be assigned and deleted.

    for a toy example:

       def foo():
          x = 1
          del x
          print(x)

       foo()

Do the same for free variable.

    def multiplier(n):
	def multiply(x):
	    return x * n
	del n
	return multiply

The error message can be improved by adding "or got deleted".
msg288024 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2017-02-17 17:11
Wouldn't be better to say just "local variable 'xxx' is not set"?
msg288032 - (view) Author: Matthias Bussonnier (mbussonn) * Date: 2017-02-17 18:30
> Wouldn't be better to say just "local variable 'xxx' is not set"?

Thank you for responding that fast and for your interest in this issue. 

While I think that "local variable 'xxx' is not set" is technically correct, I think it would be a step back.

The improvement of the error message is not really meant for the 99% of the developers hanging on b.p.o. I think for these just the fact that there is an UnboundLocalError is sufficient to know what's happening. 

The error message will mostly be useful for users that still don't have much clue about what's going on. For these kind of users UnboundLocalError("local variable 'xxx' is not set") is not much different from NameError("name 'xxx' is not defined")[*]

The current error message already does a good job at giving some extra hint as to why this is the case. In particular that it is (often) assigned but not in the right place. Changing to "local variable 'xxx' is not set" will likely make many novices check the spelling, and be more confused.

Note that "local variable 'xxx' referenced before assignment, or got deleted" would not be completely obvious for novices either, why would the following not trigger such an error ? 

In [1]: def foo():
   ...:     if False:
   ...:         print(n)
   ...:     n = 1

Well, for you and me it's (likely) obvious. 

I want to point out (just stealing rhettinger example) that it might not be obvious either the variable has been deleted:

In [5]: def foo():
   ...:     n = 1 
   ...:     def g(): # imagine a more complex g
   ...:         del n 
   ...:     g()
   ...:     print(n) #UnboundLocalError
   ...:

So I think expanding the error message to give hints as of _why_ is better than just stating what is. 


[*] Source: The users I work with. Last question I got on wednesday: "Can you re-explain the difference between a string and a function". They meant "list" and "list comprehension".
msg288033 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2017-02-17 18:55
Adding "or got deleted" makes the error message longer, and since "del" is rarely used for deleting local variables I afraid that it can add confusion in most cases.

The "del" statement is not the only way to unset the local variable.

>>> def foo():
...     e = None
...     try:
...         1/0
...     except Exception as e:
...         pass
...     print(e)
... 
>>> foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in foo
UnboundLocalError: local variable 'e' referenced before assignment
msg288038 - (view) Author: Matthias Bussonnier (mbussonn) * Date: 2017-02-17 20:37
> makes the error message longer

Out of curiosity is there a guide as to what is considered a too long error message ?

Or is there a technical reason like after a certain length strings are not cached and exception will be slower to construct ? I'm trying to understand why rhettinger would have proposed in his talk to make this specific error message longer and there is a push back now. 

> and since "del" is rarely used for deleting local variables I afraid that it can add confusion in most cases.

> The "del" statement is not the only way to unset the local variable.

Good point, would "or has been unset" be acceptable ?
msg288041 - (view) Author: Martin Panter (martin.panter) * (Python committer) Date: 2017-02-17 21:20
Matthias’s proposal sounds reasonable to me. There is a minor disadvantage that it will exceed the width of an 80-character terminal:

UnboundLocalError: local variable 'x' referenced before assignment, or got delet
ed

But I don’t think the wrapping is a big deal; it already happens with longer variable names anyway. I doubt using “unset” instead of “deleted” is any better. If you really wanted to make it clear, you would have to say “or got deleted, perhaps by an exception handler”, but that is probably going too far.

Another idea, which separates the hint from the technical error:

UnboundLocalError: local variable 'x' not set; perhaps it is not yet assigned
msg289165 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2017-03-07 15:40
See also issue17792.
msg300399 - (view) Author: Vedran Čačić (veky) * Date: 2017-08-17 04:47
Re: http://bugs.python.org/msg288032

I don't know what example Raymond had, but your example

In [5]: def foo():
   ...:     n = 1 
   ...:     def g(): # imagine a more complex g
   ...:         del n 
   ...:     g()
   ...:     print(n) #UnboundLocalError

is completely wrong, at least in what you imply in the comments. It would be a horrible language where functions could just delete nonlocal names in the same way as local ones. (You can force it by adding `nonlocal n` inside g. But "nonlocal keyword is ugly for a good reason", to quote Guido.:)

It's interesting that in your example, you also get an ULE, but for a whole different reason: it is _g_'s call that tries to _delete_ (a kind of "referencing") a local (to _it_) variable n, that hasn't yet been set _in the scope of g_. You can see it easily in the traceback, and you'd probably see it if you didn't have preconceived notions about what happens. :-]

You can easily see it by removing print(n), it is a red herring, or even more simply, just define g globally:

    >>> def g(): del n
    >>> g()
    UnboundLocalError: ...

You'll get the same phenomenon.

---

About the error message: I agree with Serhiy. See http://www.drmaciver.com/2013/07/a-case-study-in-bad-error-messages/#comment-9095 (and the whole discussion if you miss the context). Trying to cover all the possible situations where an ULE might get raised is (a) futile, (b) worsening things, since experienced programmers already know that error messages are just approximations, and beginners are best helped with just the most common case.

May I ask why do you even teach "del on simple names" (as opposed to del on list items, which is kinda useful since it has a sideeffect) to beginners? It's very rarely needed, especially in a language with a powerful garbage collector.
msg300452 - (view) Author: Matthias Bussonnier (mbussonn) * Date: 2017-08-17 18:06
Thanks you all for your input, it seem like to much of electronic ink and time has been spend on this issue. For the sake of everyone and avoiding more to be spent, I'm going to close it.
History
Date User Action Args
2022-04-11 14:58:43adminsetgithub: 73779
2017-08-17 18:06:14mbussonnsetstatus: open -> closed
nosy: barry, rhettinger, r.david.murray, martin.panter, serhiy.storchaka, veky, levkivskyi, mbussonn
messages: + msg300452

resolution: duplicate
stage: resolved
2017-08-17 04:47:50vekysetnosy: + veky
messages: + msg300399
2017-08-16 22:18:41barrysetnosy: + barry
2017-03-21 13:21:22levkivskyisetnosy: + levkivskyi
2017-03-07 15:40:29serhiy.storchakasetmessages: + msg289165
2017-02-17 21:20:15martin.pantersetmessages: + msg288041
2017-02-17 20:37:35mbussonnsetmessages: + msg288038
2017-02-17 18:55:14serhiy.storchakasetmessages: + msg288033
2017-02-17 18:30:21mbussonnsetmessages: + msg288032
2017-02-17 17:11:32serhiy.storchakasetnosy: + rhettinger, serhiy.storchaka, r.david.murray, martin.panter
messages: + msg288024
components: + Interpreter Core
2017-02-17 17:06:57mbussonncreate