Python does not tell me which line an undefined function is in

Python 3.12 on Windows 10. I’m still fairly new to Python. I wouldn’t assume I know something. :slight_smile: I also have several project directories, each with their own virtual environment.

I got this error when running my program in debug mode using the pdb debugger:

-> if checkaddresses(addrlist)>0:
(Pdb) n
NameError: name 'checkemailaddr' is not defined

The function checkaddresses(addrlist) calls checkemailaddr, which is not defined because it is misspelled. The spelling should be “checkemailaddress”. Python tells me which line checkaddresses was called, but not where it tried to call the undefined checkemailaddr().

Is there a way to improve this error message?

EDIT: The misnamed function “checkemailaddr” is in my custom library called “ggutil.py” which is in it’s own directory, configured as a package as well. This is how I import that file in all my programs.

sys.path.append(r"c:/users/USERNAME/Pycharmprojects/ggutil2024") # Required to use ggutil
try:
    from ggutil import * # Custom utils. Must go last.
except Exception as e:
    print('ERROR: could not import ggutil.',e)
    sys.exit()

Other import methods did not seem to work to import ggutil.

The information should be in the traceback. Can’t you get the traceback somehow in pdb? (Quick check: the w command seems to be the one you’d need).

No, the info is not in the traceback. The undefined function is actually in my personal library file called “ggutil.py” which I import. This library file will be used with all my programs.

  1. Keeping one library file for every different program would be a nightmare.
  2. Installing my ggutil in every virtual env for every would also be a pain.

Here is a minimal, complete, and reproducible example:

>>> def test():
...     hello()
... 
>>> pdb.run("test()")
> <string>(1)<module>()->None
(Pdb) n
NameError: name 'hello' is not defined
> <string>(1)<module>()->None
(Pdb) n
--Return--
> <string>(1)<module>()->None
(Pdb) n
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.10/pdb.py", line 1607, in run
    Pdb().run(statement, globals, locals)
  File "/usr/lib/python3.10/bdb.py", line 598, in run
    exec(cmd, globals, locals)
  File "<string>", line 1, in <module>
  File "<stdin>", line 2, in test
NameError: name 'hello' is not defined. Did you mean: 'help'?

Yep. That’s what I’m getting. def test() should be in the main program .py file, and the error should tell me the line where the missing hello() is.

I figured this might just be an oversight.

I wonder if the traceback is having issues with this method of importing your utils. Have you tried configuring a site-specific path configuration file so you can import without needing to append to sys.path in the script?

I will look into it.

I have a different directory on Windows for each project and each project has its own virtual environment. How would this site config thing interact with the virtual environment since activating the virtual env changes some environment variables?

I believe you can create the .pth file inside the virtual environment? If you look inside the folder where one of your virtual environments is stored, the directory structure should be much like how it is described in the site docs @jamestwebber linked. If you place your site config within the virtual environment it will be used when the virtual environment is active.

I’m not familiar with doing this myself so I think I misread the documentation–it seems like the way it sets the search paths makes it tricky to share custom code across venvs without enabling the rest of system site-packages. So you’d have to add the .pth file each time, which isn’t fun–but it’s not more work than copying the sys.path snippet, I suppose.

This clarfies things. The reason you didn’t see a traceback with the line number of the error is because you’re catching (suppressing) the exception instead of letting it propagate up to the default handler. Just printing the exception object only shows you the object’s repr, not the full traceback. (If for whatever reason you want to generate a formatted traceback for an exception object, you can use the tools in traceback).

Generally, it’s not a great idea to catch an exception unless one of the following is true:

  1. You want to raise a more specific exception that provides additional information about what went wrong
  2. You want to perform some action before letting the exception continue to propagate (e.g. closing resources, logging)
  3. You know exactly how to recover from it or otherwise know that its innocuous (obligatory Zen: In the face of ambiguity, refuse the temptation to guess)

Catching exceptions prematurely (especially if only to print a message and exit) has the effect of discarding information that could be useful for debugging. Note that the stack trace for an exception is built up as the exception bubbles up through the call stack, so the further you let an exception propagate, the more information you’ll have about where your program was at the time the exception was raised.

There’s some more discussion on when to catch exceptions in this related thread:

If you have virtual environments for your different projects, probably the easiest thing to do would be to install your library in each one. That should be as simple as adding a minimal pyproject.toml and then running

pip install -e 'c:/users/USERNAME/Pycharmprojects/ggutil2024'

once for each virtual environment. Note the -e flag for editable mode, which more-or-less just permanently appends c:/users/USERNAME/Pycharmprojects/ggutil2024 to the virtual env’s import path.

You could also add c:/users/USERNAME/Pycharmprojects/ggutil2024 as a path dependency in your other projects’ pyproject.toml files.

The exception isn’t being caught. The file is being imported just fine, the exception is inside ggutil.

The purpose of the above code was to catch an exception during the execution of the import line, to see if my app had my custom utils installed correctly.

Does that particular try...except code catch and process all exceptions that happen in ggutil.py?

It will catch all exceptions that occur during import of ggutil. Importing will execute the file, so anything at the top level (e.g. not inside a function, or protected by if __name__ == "__main__") will be executed. To make this try/catch block a little better, you could specifically catch ImportError instead of Exception. Then, if there are other exceptions in your util file, they’ll raise a normal error.

If the line if checkaddresses(addrlist)>0 was actually in ggutil, and that’s when the exception was raised, then you would have swallowed the exception.

Instead, it appears that the import went fine, but you call one of your util functions later and it raises an exception. You’re not catching it.

Testing this out myself, I wonder if you just need to use more of the debugger commands to get the info you want. I made a test script with an error and imported it via first adding the location to sys.path. If I run the script directly, the traceback tells me where the error was. If I use python -m pdb I see the error message with no line number, but if I then type w (for where) I get the location in my test file.

2 Likes

No, that was my misreading of the original problem. I thought that the exception was being raised during the import of ggutil. Still, most of the same points apply — if an exception were raised at import time, letting the expcetion propogate could give you more information about where and why.