import tempfile
tmp = tempfile.NamedTemporaryFile(delete=True)
try:
# do stuff with temp
tmp.write(b'def fun():\n\tprint("hello world!")\n')
if __name__ == '__main__':
func = __import__(tmp.name)
func.fun()
finally:
tmp.close() # deletes the file
So I want to create a temporary file, add some source code to it and then import the module and call the function, but I always run into this error:
ModuleNotFoundError: No module named '/var/folders/3w/yyp887lx4018h9s5sr0bwhkw0000gn/T/tmp84bk0bic'
It doesn't seem to find the module of the temporary file. How do I solve this?
There are a few problems with your code:
Your filename does not end with .py, but Python modules are expected to. You can fix this by setting suffix='.py' in NamedTemporaryFile().
__import__() is not the right way to load a module from a full path. See here: How to import a module given the full path?
You do not flush after writing and before importing, so even if Python does find the file, it may well be empty. Add tmp.flush() after writing to fix this.
Importing can only be done from certain directories which are part of the PYTHON_PATH. You can extend that. Then you will have to use __import__() with a module name (not a path in the file system). You will have to deal with the suffix for the temp file.
I implemented a simple version using the local directory for the temp module file and a version using a proper tempfile:
#!/usr/bin/env python3
import sys
import os
import tempfile
SCRIPT = '''\
def fun():
print("hello world!")
'''
# simple version using the local directory:
with open('bla.py', 'w') as tmp_module_file:
tmp_module_file.write(SCRIPT)
import bla
bla.fun()
# version using the tempfile module:
tmpfile = tempfile.NamedTemporaryFile(suffix='.py', delete=True)
try:
tmpfile.write(SCRIPT.encode('utf8'))
tmpfile.flush()
tmpmodule_path, tmpmodule_file_name = os.path.split(tmpfile.name)
tmpmodule_name = tmpmodule_file_name[:-3] # strip off the '.py'
sys.path.append(tmpmodule_path)
tmpmodule = __import__(tmpmodule_name)
finally:
tmpfile.close()
tmpmodule.fun()
Related
I define parameters for my programs in a python module for simplicity. These parameters are then loaded using import. Therefore, I have to make sure to always load from the working directory, and nowhere else (independent form the location of the executed script or available modules in python path).
I found two solutions. First modifying the path:
import sys
from os import getcwd
import_path = sys.path
sys.path = [str(getcwd(), ]
import xxx
sys.path = import_path
or using importlib
from pathlib import Path
from importlib.util import module_from_spec, spec_from_file_location
spec = spec_from_file_location('xxx', str(Path('.').expanduser()/'xxx.py'))
xxx = module_from_spec(spec)
spec.loader.exec_module(xxx)
Of course this can be wrapped into a context manager or a function respectively.
What would the pythonic way be to do this? Do those two approaches have advantages and disadvantages?
I checked How can I import a Python library located in the current working directory?
as well as Import python package from local directory into interpreter, but they lack the focus on robustness.
Local imports can be achieved by modifying the path. A context manager is a suitable solution:
import sys
from pathlib import Path
from contextlib import contextmanager
#contextmanager
def local_import(dir_=None):
"""Only import modules within `dir_` (default: cwd)."""
if dir_ is None:
dir_ = Path.cwd()
else:
dir_ = Path(dir_).absolute().resolve(strict=True)
import_path0 = sys.path[0]
sys.path[0] = str(dir_)
try:
yield
finally:
sys.path[0] = import_path0
Then the local import can be done using the standard import syntax
with local_import():
import xxx
This solution relys on the order, in which the paths are scanned, thus we temporarily replace sys.path[0]. We replace it, instead of prepending to avoid import conflicts with the script directory.
Note
You have to be careful to avoid name conflicts, as the import statement is used, modules with identical names will be imported only once. Thus if different modules with the same name exist in the working directory and in the original sys.path[0], only one of them will be imported. Thus, local_import should only be used for scripts, that only use the standard library or installed third party libraries, but not for scripts that import other scripts from the directory. For the unlikely case that you want to import different files with the same name, the following function can be used:
import uuid
from importlib.util import module_from_spec, spec_from_file_location
def import_file(file, content=None):
"""Try importing `file` as module avoiding name clashes.
If `content` is given `content = import_file('file.py', 'content')`
roughly corresponds to `from file import content`
else `file = import_file('file.py')`
roughly corresponds to `import file`.
Parameters
----------
file : str or Path
The Python file corresponding to the module.
content : str, optional
What to import from the module (optional).
"""
file = Path(file).expanduser().resolve(strict=True)
print(file)
spec = spec_from_file_location(file.stem + str(uuid.uuid4()), str(file))
module = module_from_spec(spec)
spec.loader.exec_module(module)
if content:
print(module)
return getattr(module, content)
else:
return module
I have the following structure
main.py
module/
properties.yaml
file.py
file.py relevant code:
def read_properties():
with open('properties.yaml') as file:
properties = yaml.load(file)
main.py relevant code:
from module import file
file.read_properties()
When read_properties() is called within main.py, I get the following error: FileNotFoundError: [Errno 2] No such file or directory: 'properties.yaml'
What is the recommended way of allowing my module to access the properties file even when imported?
Provide the absolute path to properties.yaml:
with open('/Users/You/Some/Path/properties.yaml') as file:
As JacobIRR said in his answer, it is best to use the absolute path to the file. I use the os module to construct the absolute path based on the current working directory. So for your code it would be something like:
import os
working_directory = os.path.dirname(__file__)
properties_file = os.path.join(working_directory, 'module', 'properties.yaml')
Based on answers from #JacobIRR and #BigGerman
I ended up using pathlib instead of os, but the logic is the same.
Here is the syntax with pathlib for those interested:
in file.py:
from pathlib import Path
properties_file = Path(__file__).resolve().parent/"properties.yaml"
with open(properties_file) as file:
properties = yaml.load(file)
So I've been trying to make a simple program that will dynamically import modules in a folder with a certain name. I cd with os to the directory and I run module = __import__(module_name) as I'm in a for loop with all of the files names described being iterated into the variable module_name.
My only problem is I get hit with a:
ImportError: No module named "module_name"
(saying the name of the variable I've given as a string). The file exists, it's in the directory mentioned and import works fine in the same directory. But normal even import doesn't work for modules in the cd directory. The code looks as follows. I'm sorry if this is an obvious question.
import os
class Book():
def __init__(self):
self.name = "Book of Imps"
self.moduleNames = []
# configure path
def initialize(self):
path = os.getcwd() + '/Imp-pit'
os.chdir(path)
cwd = os.walk(os.getcwd())
x, y, z = next(cwd)
# Build Modules
for name in z:
if name[:3] == 'Imp':
module_name = name[:len(name) - 3]
module = __import__(module_name)
def start_sim():
s = Book()
s.initialize()
if __name__ == '__main__':
start_sim()
I don't think the interpreter dynamically alters sys.path if you simply change the current directory with os.chdir. You'll manually have to insert the path variable into the sys.path list for this to work, i.e:
sys.path.insert(0, path)
Python generally searches sys.path when looking for modules, so it will find it if you specify it there.
An additional note; don't use __import__, rather use importlib.import_module. The interface is exactly the same but the second is generally advised in the documentation.
You should use the try-except concept, e.g.:
try:
import csv
except ImportError:
raise ImportError('<error message>')
If I did understand you correct then
try:
module = __import__(module_name)
except ImportError:
raise ImportError('<error message>')
I have a directory which has all the files:
myDirectory/
directory1/
importantFile.py
Output.py
How can I import Output.py from importantFile.py without having to put in the same directory?
importantFile.py
import Output
Output.write('This worked!')
Output.py
class Output():
def writeOutput(s):
print s
if "call" is import, in Output.py
import sys
import os.path
# change how import path is resolved by adding the subdirectory
sys.path.append(os.path.abspath(os.getcwd()+'/directory1'))
import importantFile
importantFile.f()
sys.path contains the list of path where to look for modules, details in https://docs.python.org/2/library/sys.html
The other way is to use the relative notation, for which the python file you want to import should be in a package.
You have to make the directory a python package by putting an init.py file.
Look for the packages section in this link.
https://docs.python.org/2/tutorial/modules.html
import sys
sys.path.append('/full/path/to/use')
global exist_importedname
exist_importedname = True
try:
import myimport
except ImportError as e:
exist_importedname = False
print (e.message)
please help convert the variable "fileNameClean" so that you can open the file via the module "shelve"
import shelve
fileName = 'C:/Python33/projects/DVD_LIST/p3_dvd_list_shelve_3d_class_edit_menubar/data.dir'
print('___', fileName)
str = fileName.split('/')[-1]
print('--', str)
fileNameClean = str.split('.')[0:-1]
print(fileNameClean) #['data']
db = shelve.open(fileNameClean) #open error
Use the os.path module to produce a clean path:
import os.path
fileName = 'C:/Python33/projects/DVD_LIST/p3_dvd_list_shelve_3d_class_edit_menubar/data.dir'
fileNameClean = os.path.splitext(os.path.basename(fileName))[0]
db = shelve.open(fileNameClean)
Your code also produced a base name minus the extension, but you returned a list by using a slice (from index 0 until but not including the last element). You could have used fileNameClean[0], but using the os.path module makes sure you catch edgecases in path handling too.
You probably want to make sure you don't use a relative path here either. To open the shelve file in the same directory as the current script or module, use __file__ to get an absolute path to the parent directory:
here = os.path.dirname(os.path.abspath(__file__))
db = shelve.open(os.path.join(here, fileNameClean))