Importing Python By Path

February 17th, 2011
python, tech

Update 2012-01-06: Warning: this post is what you get if you don't know about the function __import__ and try to reinvent it.

Imagine I have two different python files that are drop-in replacements for each other, perhaps for some kind of plugin system:

  $ cat a/c.py
  def d():
     print "hello world a"
  $ cat b/c.py
  def d():
     print "hello world b"
  
And further imagine I want to use both of them in the same program. I might write:
  import c
  c.d()
  import c
  c.d()
  
But that has no chance of working. Python doesn't know where to find 'c'. So I need to tell python where to look for my code by changing sys.path:
  sys.path.append("a")
  import c
  c.d()
  sys.path.append("b")
  import c
  c.d()
  
This will work, ish. It will print "hello world a" twice. Part of this is that the path "a" is sys.path before "b". I really only want this sys.path change to last long enough for my import to work. So maybe I should do:
  class sys_path_containing(object):
     def __init__(self, fname):
         self.fname = fname

     def __enter__(self):
         self.old_path = sys.path
         sys.path = sys.path[:]
         sys.path.append(self.fname)

     def __exit__(self, type, value, traceback):
         sys.path = self.old_path

  with sys_path_containing("a"):
     import c

  c.d()

  with sys_path_containing("b"):
      import c

  c.d()
  
This is closer to working. I define a context manager so that code run with sys_path_containing will see a different sys.path. So my first "import c" will see a sys.path like ["foo", "bar", "a"] and my second import will see ["foo", "bar", "b"]. Each is isolated from the other and from other system changes. Unfortunately, it still won't work, because python remembers what it has imported before and doesn't do it again, this will still only print "hello world a" twice. Switching the second "import c" to a "reload(c)" does fix this problem, but at the expense of you already having to know whether something is loaded. Switching to "del sys.modules['c']" and using __import__ would work, though. Let's make that change and put it all into a context manager that does most of the work for us:
  class imported(object):
     def __init__(self, fname):
         self.fname = os.path.abspath(fname)

     def __enter__(self):
         if not os.path.exists(self.fname):
             raise ImportError("Missing file %s" % self.fname)

         self.old_path = sys.path
         sys.path = sys.path[:]

         file_dir, file_name = os.path.split(self.fname)
         sys.path.append(file_dir)

         file_base, file_ext = os.path.splitext(file_name)
         module = __import__(file_base)
         del sys.modules[file_base]

         return module

     def __exit__(self, type, value, traceback):
         sys.path = self.old_path

  with imported("a/c.py") as c:
     c.d()

  with imported("b/c.py") as c:
     c.d()
  
This will print "hello world a" and then "hello world b". Yay!

Comment via: substack

Recent posts on blogs I like:

Raffles

I think that raffles are kind of silly, because when you enter a raffle and you don't know what you're going to get, or if you're going to get anything, what's the point? If you pay for your raffle ticket, and you could pay the same amount…

via Anna Wise's Blog Posts September 13, 2026

Air Filters in Schools

My dad went to a conference recently, and when I asked him about what happened there he said it was a conference for people in biosecurity. When I asked him about the different kinds of biosecurity stuff that the different groups did, he said one did air f…

via Lily Wise's Blog Posts September 12, 2026

The One That I Want Another Sister

I want a baby sister or brother. Because I want to play with another kid, and I don't want to play with Josie because she is in preschool and I won't play with her very often. So I want to play with someone very often who is not just two people. …

via Nora Wise's Blog Posts September 5, 2026

more     (via openring)