3

I'm using Python 3.3.2 to write a package that encapsulates a filesystem. My project looks like this:

~/
  python/
    filesystem/
      __init__.py
      file.py
      directory.py

With PYTHONPATH=~/python.

The problem is, file.py needs directory.py (for example, for File.get_directory()), and directory.py needs file.py (for example, for Directory.get_files()), so I have a circular import.

  1. When I use import directory in file.py, and import file in directory.py, it only works when my working directory is filesystem (that is, when the imports are local).
  2. When I use import filesystem.directory in file.py, and import filesystem.file in directory.py, it works fine, except the aesthetic nuisance of writing filesystem.file.File and filesystem.Directory.directory all them time.
  3. Curiously, when I use import filesystem.directory as directory or from filesystem.directory import Directory, I get the circular import error 'module' object has no attribute 'directory'. My guess is that while import ... is lazy, import ... as and from ... import attempt to evaluate the module and notice the circularity immediately.
  4. One way to solve this is to import filesystem.directory inside the functions that use it. Unfortunately, many of my methods use it, and importing it inside a class does not seem to work.

This is solvable, of course: sucking it up and writing filesystem.directory.Directory; assigning an __import__ to a global variable in the __init__ method for all the other methods to use; defining File and Directory in the same file; and so on. But these are more compromises than solutions, so my questions still stand:

  1. How would you design a filesystem, where a file class uses the directory class, and vice versa?
  2. And more generally, how would you deal with (or avoid) circular imports?

Thanks.

UPDATE [03.07.2013] (Mostly for discussion's sake)

Another solution I came upon is some sort of forward declarations, with empty file and directory classes in a common header, followed by separate implementations (more like attributes addition). While the resulting design is very neat, the idea is more C++-ish than Pythonic.

Dan Gittik
  • 3,460
  • 3
  • 17
  • 24
  • Dealing with circular imports took me a while. I really had to graph out my modules (by hand) into a tree structure, making sure I was only carrying my dependencies one direction. Once I started doing that before larger project decisions were being made, it really cut down on the refactoring I had to do later. – BlackVegetable Jul 01 '13 at 21:27
  • @BlackVegetable - how would you design the described filesystem without circular imports? It looks inherently circular to me, so I would love to hear some outside-the-box alternative designs. – Dan Gittik Jul 03 '13 at 17:27

1 Answers1

1

Using a fully-qualified path isn't really a hack; it's probably the proper solution to this particular issue. If you want a shorter name to type out, you could do something like this:

import filesystem.directory

class File(object):

   def __init__(self):
       self._Directory = filesystem.directory.Directory

   def foo(self):
       some_dir = self._Directory(...)

This also makes it trivial to swap in a mock if you're testing, et cetera.

Amber
  • 507,862
  • 82
  • 626
  • 550
  • Thanks for the answer. These are basically the "sucking it up and writing `filesystem.directory.Directory`" and "assigning an `__import__` to a global variable in the `__init__` method for all the other methods to use" solutions, and I guess the fully-qualified path is indeed the proper solution, but I'm still looking for alternatives, even if only out of academic curiosity. – Dan Gittik Jul 03 '13 at 17:33