Qix-/better-exceptions: readable Python tracebacks through a .pth hook
Pretty and useful exceptions in Python, automatically.
At a glance
- What is it?
- A Python package that replaces the default excepthook to print source lines and variable values. It installs through a .pth file and stays dormant until BETTER_EXCEPTIONS is set.
- Who is it for?
- Adopt better-exceptions for local development and debugging sessions where seeing variable values inside a traceback saves a round trip to the debugger. Do not adopt it for production logging: the README itself warns to unset BETTER_EXCEPTIONS there to avoid leaking sensitive data.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 85 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 2, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem: a traceback that names the line but not the values
A standard Python traceback tells you which file and line raised, and nothing about what the variables held when it happened. For a crash inside a loop over a list of dicts, that is often the whole question. better-exceptions targets exactly that gap: it reformats the exception output so each frame shows the source line and, where the implementation can read them, the local values. The audience is a developer running code in a terminal or a REPL, not an operator reading aggregated logs. The README frames the package as "Pretty and more helpful exceptions in Python, automatically", and the screenshot in the repository root is the claim being made. Nothing in the repository describes a hosted service, a collector, or an agent: this is a formatting layer that sits on the process's exception path.
How the hook activates: a .pth file plus an environment variable
The mechanism is visible in the repository layout and in setup.py. The top level contains better_exceptions_hook.pth, and the setup script defines four command subclasses (BuildWithPTH, EasyInstallWithPTH, InstallLibWithPTH, DevelopWithPTH) whose only job is to copy that file into the install directory alongside the package. A .pth file in site-packages is processed by Python at interpreter startup, so the hook is registered before user code runs. Activation is then gated on the BETTER_EXCEPTIONS environment variable: with it unset, the package is inert. The README's troubleshooting section confirms the arrangement from the other direction, telling you to check that you have not deleted better_exceptions_hook.pth and that sys.excepthook has actually been replaced. That design has a consequence worth stating plainly: because the hook is installed at interpreter startup rather than imported by your code, there is no import statement to grep for when you are trying to work out why tracebacks look different on one machine and not another.
Install and first run with BETTER_EXCEPTIONS
Installation is a pip install, and the README gives the environment variable as the only required configuration. On Linux and macOS the export applies to the current shell session; the README notes it does not persist, and that you will probably need to edit ~/.profile for that. On Windows the command is setx, and the README states you need to open a new terminal afterwards for it to take effect.
pip install better_exceptionsexport BETTER_EXCEPTIONS=1 # Linux / OSX
setx BETTER_EXCEPTIONS 1 # WindowsAfter that, run a script that raises. What you should see is the reformatted traceback rather than the interpreter's default one. If you do not, the README's first troubleshooting step is to confirm the variable is actually set, with echo $BETTER_EXCEPTIONS on Linux and macOS or echo %BETTER_EXCEPTIONS% on Windows. The second step is to check for a conflicting exception handler. The third is to verify the .pth file is present next to the package directory in site-packages. As a last resort the README offers manual activation, which is also the cleanest option when you want the behaviour in one script rather than every process on the machine:
import better_exceptions
better_exceptions.hook()The README also documents a REPL entry point, python -m better_exceptions, which drops you into a shell whose prompt reads (BetterExceptionsConsole).
Truncation, MAX_LENGTH, and the production warning
By default the formatter truncates values, which is a sensible default for a terminal but a real constraint when the value you need is the long one. The README exposes a single module-level setting for this: assigning None to better_exceptions.MAX_LENGTH removes the truncation and allows the entirety of values to be output. There is no per-call or per-frame control documented, so the choice is global for the process. The same section carries the limitation that matters most: while using better-exceptions in production, the README says not to forget to unset BETTER_EXCEPTIONS to avoid leaking sensitive data in your logs. That is not a hypothetical. A traceback with local variable values can contain credentials, tokens, or personal data, and this package's whole value proposition is putting those values into the output. Treat the environment variable as a development-only switch, and treat any deployment where it is set as a decision to write locals into whatever consumes stderr.
Django and unittest integrations, and where they are fragile
Two integrations are documented. For Django, you add better_exceptions.integrations.django.BetterExceptionsMiddleware to MIDDLEWARE and adjust LOGGING. The README is explicit about why the logging change is needed: without the skip_errors filter, Django logs errors twice, once through better-exceptions and once through its own unformatted handler. The filter comes from better_exceptions.integrations.django, and the README's example registers it as a django.utils.log.CallbackFilter named skip_errors on the console handler. It also points at Django's own default logging configuration for 3.1.4 as the thing to vendor if you do not want to override LOGGING wholesale. That is a fair amount of configuration for a formatting change, and it is the price of not fighting Django's existing error path.
The unittest integration is weaker, and the README says so. It is a monkey patch that assigns a new function to unittest.result.TestResult._exc_info_to_string, and the README labels it an undocumented method override that is not guaranteed to work on all platforms or versions of Python. If you depend on unittest output formatting, that caveat should be read as a maintenance risk rather than boilerplate: the patch reaches into a private attribute, and nothing in the repository layout suggests a compatibility layer around it.
Conflicts with other excepthook owners
better-exceptions works by owning sys.excepthook, and anything else that wants that slot is a competitor. The README names one concrete example: the python3-apport package on Ubuntu, which the troubleshooting section says you may need to uninstall. This is the failure mode to expect in the wild. A process that installs its own handler after startup, or a distribution that ships an exception reporter, will silently win, and the symptom is simply that tracebacks look normal. The troubleshooting order in the README is well matched to that: check the variable, check for a conflicting library, check that sys.excepthook was replaced, check the .pth file. If you are debugging in an environment you do not control, the manual better_exceptions.hook() call is more predictable than relying on startup ordering. The package is also the wrong tool when the traceback is not the artifact you consume. If your errors go to a structured logging pipeline, an APM agent, or a crash reporter, formatting stderr changes nothing about what those systems record, and the value of this package is limited to the human reading a terminal.
Maintenance status, licence, and upgrade cost
The repository is not archived, and the last push was on 2026-07-10. The most recent release listed is 0.2.1 from 2018-01-16, with 0.1.8 before it in 2017. That combination is worth reading carefully: there is recent commit activity, but the published version has not moved in years, so a pip install gets you the 0.2.1 code rather than whatever is on master. If a fix you need landed after that release, installing from the package index will not pick it up. The licence is MIT, with the README stating copyright 2017, Josh Junon, and the repository carrying LICENSE.txt. MIT is permissive, so redistribution and modification are allowed subject to the licence text; that is a statement about the licence, not legal advice, and anyone embedding this in a product should read LICENSE.txt themselves. The upgrade cost is low in one sense, because there is a single documented setting (MAX_LENGTH) and an environment variable, but the .pth install mechanism means an upgrade rewrites a file in site-packages, so environments that pin or vendor site-packages contents should account for that.
Editorial conclusion
Adopt better-exceptions for local development and debugging sessions where seeing variable values inside a traceback saves a round trip to the debugger. Do not adopt it for production logging: the README itself warns to unset BETTER_EXCEPTIONS there to avoid leaking sensitive data. Before installing, verify that nothing else in your process has already replaced sys.excepthook, since the troubleshooting section names python3-apport as one such conflict, and confirm that better_exceptions_hook.pth lands next to the package in site-packages.
Frequently asked questions
How do I install better-exceptions?
Install it with pip install better_exceptions, then set the BETTER_EXCEPTIONS environment variable to any value. On Windows the README uses setx and notes you must open a new terminal afterwards.
Why is better-exceptions not working?
The README's troubleshooting order is to confirm the environment variable is set, check for a conflicting exception handler such as the python3-apport package on Ubuntu, verify that sys.excepthook was replaced, and make sure better_exceptions_hook.pth is still present next to the package in site-packages. As a fallback you can call better_exceptions.hook() manually at the start of your script.
How do I use better-exceptions in the Python REPL?
Run python -m better_exceptions after installing the package. The README shows the resulting prompt as (BetterExceptionsConsole).
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/qix-better-exceptions)