Remember when I said how great it was to make a IPython startup file? Remember those good ego feelings? Yeah, well, I went on vacation and came back and it broke. Goddammit!
Not only did it stop working, but it stopped working in a weird, dark magic way.
Friendly reminder
The point of the IPython startup file was that, every time I opened a new ipython
shell or Jupyter notebook, it would automagically (and, in the notebooks, silently) import a bunch of packages I wanted (pandas
, numpy
, etc.) as well as a couple convenience functions (e.g. alert()
). This would spare me having to copy that cell over every time. Hence, it was a minutes-saving, productivity-enhancing tool for near-daily use.
The problem is that it fails silently in the notebook (it fails noisily in the ipython
shell). So, today, I happily opened a new notebook, double-checked a couple of my expected packages (pd
returned the pandas
object, we are go for launch), and proceeded.
And then - it broke. Specifically, my notebook complained that it had never heard of alert()
. Huh, what now? It's in the startup file. How can you see import pandas
but not def alert()
? How does that make any sense?!
Fixing it, step 1: ipython
> notebook
No error message means no clue, so I had to find the error message. The first place I looked was the Jupyter notebook terminal output:
Uh, what? [IPKernelApp] WARNING | Unknown error in handling startup files:
... and nothing?
This is where ipython
shell is handier:
Ooooh.
Note: That
sqlalchemy
import is not the actual error. I had a much weirder error in a package I had written. Namely...
Fixing it, step 2: circular imports because I no design software so good
I've had something of a dark obsession with design patterns in software for the past ~8 months. Mainly that I don't know any, but I know they're out there. My colleagues always talk about "code smells". WHAT IS A CODE SMELL? Things like that. I watch YouTube videos. This one by Sandi Metz was good - but I just don't write that much Actual Software to have enough practical experience to know this stuff. So I'm always hungry to learn more and get better at it.
One "pattern" (SCARE QUOTES) I've decided to adopt is putting global vars at the very tip-top of the American pyramid my Python package, i.e. in the __init__.py
. Then I can import them in all sundry modules within the package. So I had something like this:
# __init__.py
from package import module1
from .top_module import Class1, Class2
# Global vars
STATISTICAL_SIGNIFICANCE = 0.95
DEFAULT_POWER = 0.9
But then, in my module1.py
, I had this:
# module1.py
from . import STATISTICAL_SIGNIFICANCE, DEFAULT_POWER
Oh no. In other words, when I imported my package
, the following would happen:
package
runs__init__.py
to get everything started.- First thing, it tries to import
module1
. - In
module1
, it tries to importSTATISTICAL_SIGNIFICANCE
andDEFAULT_POWER
- Error! It doesn't know what those global vars are! Because they're defined further down the
__init__.py
.
So the __init__.py
initialization fails, and everything fails, and - because I was importing this package about halfway down into my IPython startup file, it failed and only managed to import a few things for my Jupyter notebook. As you can guess, the alert()
function definition was further down my startup file. It never got there.
So here's my fix!
# __init__.py
# Global vars
STATISTICAL_SIGNIFICANCE = 0.95
DEFAULT_POWER = 0.9
from package import module1
from .top_module import Class1, Class2
Voila! Easy! No more circular stuff. Global vars are defined first thing. Architecture! Design! SUCCESS!