Mutual import?

Alex Martelli aleaxit at yahoo.com
Mon Dec 4 11:23:51 EST 2000


"Dale Strickland-Clark" <dale at out-think.NOSPAMco.uk> wrote in message
news:r16n2tkqepahf288gmu6tku5d2sctb72o0 at 4ax.com...
> Is it considered safe and acceptable for two modules to import each other
> or are there likely to be unwelcome side effects of that?

Even if you could avoid any Python-specific problems, mutual dependencies
between components are one of the worst banes of software development.  I
would go to VERY great lengths to refactor any system based on mutual
dependencies (and highly recommend Lakos' excellent "Large Scale C++
Development" for a thorough treatment of this, albeit in a C++ context).


> The problem is that both require access to values and routines in the
other.

Refactor the system so that they don't... there are many ways to do so.

For example, the "two" modules, if they depend on each other, are 'really'
one.  Merge them into a single module.  You may still provide separate
"views" of that module 'from the outside' ('client-code'), if you want,
via two auxiliary modules (which are the ones client-code sees) which just
'from mergedmodule import' the specific list of identifiers to be exported
as belonging to the specific auxiliary-module.

Note that the "modularity" you seem to lose here (with the merging)
was never really there in the first place, because two alleged 'modules'
that actually depend on each other are too intertwined and need to be
changed/released/tested as ONE unit (i.e., you only actually have one
module anyway) -- and you may preserve appearances with the auxiliary
modules (which are the ones client-code will be seeing).


For higher-sophistication solutions, consider Robert Martin's "dependency
inversion principle" (read all he has to say about OO at his site,
http://www.objectmentor.com/publications/articlesBySubject.html -- it's
well worth the time even if one has trouble following his C++ oriented
examples, IMHO).  The general idea, using -> to denote dependency:

    we currently have A->B and B->A
    we hate that for lots of excellent reasons
    specifically, we want to remove (say) B->A, because
        B is more stable than A thus should NOT depend
        on it (that's the bad half of the mutual dependency)
    (if x->y, y should be more stable than x).

Solution: introduce another (abstract) entity C that just exposes
the "abstract interface" of A (but no implementation).  Change
the dependencies to:
    A->B    unchanged, but that's OK
    B->C    B depends on C because it *uses* that interface
    A->C    A depends on C because it *implements* that interface

This eliminates the loops and leaves all right-hand sides of a
depends-on relation as stabler than the left-hand sides (since
just-abstract-interface components can be made pretty stable
much more easily than ones doing implementation).  [Note that
Lakos' key idea for eliminating mutual dependencies is also
based on abstract-interfaces -- what he calls "protocol classes"].

Classes are the natural way to implement this (in Python just
as well as in C++), but you can do it with modules if you wish.
For example, C may be just a list of the functions A will expose,
each in the form:

-- C.py

def foo(*args, **kwds):
    global foo
    import A
    foo = A.foo
    return A.foo(*args, **kwds)

def bar(*args, **kwds):
    global bar
    import A
    bar = A.bar
    return A.bar(*args, **kwds)

-- &c

Cookie-cutter code (needed just because there is no __getattr__
in a module -- which is why a class would be handier), quite
stable indeed (it would need to change only if and when the
abstract interface of A, i.e. the set of functions A exports,
should change; its stability is thus [basically] that of an
abstract interface, as desired).

Note that this will work even if B does 'from C import *', as
it shouldn't:-), but it will be pretty inefficient then as you
will be paying a double-indirectness on each call (while, if
B has 'import C' and calls explicitly 'C.bar(fee,fie)' etc,
the double-indirectness is only paid once, at the first call,
then C's entry for 'bar' becomes identical with A's).


Alex






More information about the Python-list mailing list