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.
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 (
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:
[IPKernelApp] WARNING | Unknown error in handling startup files:... and nothing?
This is where
ipython shell is handier:
sqlalchemyimport 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:
__init__.pyto get everything started.
- First thing, it tries to import
module1, it tries to import
- Error! It doesn't know what those global vars are! Because they're defined further down 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!