A Little Gotcha with Naming Python Files
“TypeError: ‘module’ object is not callable”
I was writing a simple little Python script to recursively print out file names with a certain extension. I was using glob
to do this:
import glob
import sys
files = glob.glob(sys.argv[1] + '/**/*.png', recursive=True)
for f in files:
print(f)
For lack of a better name, I called it glob.py
. When I ran it I got:
Traceback (most recent call last):
File "/home/brian/glob.py", line 1, in <module>
import glob
File "/home/brian/glob.py", line 4, in <module>
files = glob.glob(sys.argv[1] + '/**/*.png', recursive=True)
TypeError: 'module' object is not callable
I had no idea what this meant.
Of course I used good ole’ Stack Overflow to figure out what might be going on. There were several answers in that article, but this answer described the problem I was having: the name of my Python file was the same as the glob
module I was trying to import. So when theimport glob
statement was reached my script was inadvertently importing itself, rather than the actual glob
module I wanted to import. And thus the error.
Simple solution: rename my file to something else.
But what if I wanted to keep my file name?
I tried a few things:
The PYTHONPATH environment variable.
According to python3 --help
, you can set the PYTHONPATH
environment variable, which is supposed to prepend its entries to the sys.path
variable, which is the search path for loading Python modules (as described here). However, it seems that according to that same documentation, the directory of the script being run comes before PYTHONPATH
, and in fact that is what I observed when I printed out the value of sys.path
. So in my case where I needed to deprioritize loading from the directory of my input script, this technique did not work.
Modify sys.path
I was reading that you could actually modify sys.path
yourself. So I tried doing that before importing glob
:
sys.path = ['/usr/local/python3.10'] + sys.path
import glob
(To do this, I had to find the location of the glob
module, which according to this I could get by calling glob.__file__
.) Unfortunately that also did not work, even though when I printed out the modified value of sys.path
, it did have /usr/local/python3.10
listed first. So I’m not sure why that didn’t work.
Use SourceFileLoader
This article listed a few other techniques to have more control over how modules are imported. One suggestion was to use SourceFileLoader
:
from importlib.machinery import SourceFileLoader
glob = SourceFileLoader("glob", "/usr/lib/python3.10/glob.py").load_module()
This worked for me.
Conclusions
- Try not to name your Python file with the same name as another module you are trying to import.
- If you must, then you will need to try to do some manipulation of the module import path, although it may be hard (or perhaps not possible) to get Python to not search the input Python script’s directory first.
- So try using
SourceFileLoader
instead (or one of the other techniques listed in this article).