__file__ dunder attribute works in .py, not in notebook

I am refactoring with nbdev a library that creates a web page. They use __file__ to locate web assets they want to copy. __file__ dunder attribute shows the path of a module:

When a module is loaded from a file in Python, __file__ is set to its path. (source)

Unfortunately, notebooks don’t know about this:

NameError: name '__file__' is not defined

Of course, code with nbdev should work both in a notebook and as a module.

I learned from this Github issue that globals()['dh'] gives the notebook’s directory. You can also get the cwd various ways, but people mention there’s some risks there.

I’ve resorted to this try/except block:

try: src = Path(__file__).resolve()
except NameError as e: src = globals()['_dh'][0]

Has this come up for anyone else? Any suggestions of a way to get the code’s location that works in both notebook and module environment?

Personally I just have a non-exported cell in my NB that defines it when I need it.

This would work great if I only want to copy the web assets when I’m developing the library/doing exploratory work. But in this case, the library should also copy web assets when it’s run as a pip install.

A user might do:
pip install clip-plot
clip-plot --images <folder of images>

clip-plot should generate an “embedding projector” style of visualization for their images, including copying web assets to their working directory.

try/except works as far as I can tell, so this isn’t a major issue, just wondered if there was a more standard way. Good reminder about using non-exported cells as I’m just getting into nbdev (and developing a library for that matter).

I might be missing something - but wouldn’t a pip-installed version be a module, so have __file__ available?

Yes, exactly – so I was just looking for a way to write this in the notebook originally that would also work in the pip-installed module. __file__ works in the pip-installed module, but not the notebook; globals()['_dh'] works in the notebook, but not the pip-installed module.

My suggestion works then right? In the notebook, the __file__ you define manually works, and is not exported. In the exported file, the automatically created one works.

Ah, I didn’t understand! Now I get your solution:

In one cell:

#| hide
__file__ = globals()['_dh'][0].as_posix()

In a later cell (or, to ensure this never gets executed before the hidden bit, could put in the first cell and change “hide” to “hideline”):

src = Path(__file__).resolve()

This is much more readable! Thank you! Sorry it took me a while to get my head around that.

1 Like