Problem--Extending the behavior of an upstream package

Carl Banks pavlovevidence at gmail.com
Wed Mar 11 02:19:56 EDT 2009


On Mar 10, 9:33 pm, a... at pythoncraft.com (Aahz) wrote:
> In article <60848752-2c3f-4512-bf61-0bc11c919... at i20g2000prf.googlegroups.com>,
> Carl Banks  <pavlovevide... at gmail.com> wrote:
>
>
>
> >The problem comes when a different part of the upstream package also
> >subclasses or creates a Box.  When an upstream function creates a box,
> >it creates an upstream.packaging.Box instead of a
> >mine.custom_packaging.Box, but I'd want it to do the latter.
>
> The only clean way I'm aware of is to pass in an instance and use
> instance.__class__ to create new instances.  Did you come up with
> another idea?


Ok, so after lots of false starts and muttering, and much mindbending
to get my head around the import dynamics, I came up with this simple
(not straightforward) recipe, which does not require an import hook,
but has one major drawback (that I can live with).  I defined this
function:


def adapt(base_full_mod_name):
    mp = base_full_mod_name.split('.')
    if len(mp) < 2:
        raise ValueError('can only adapt packaged modules')
    adapt_full_mod_name = '.'.join(["custom"] + mp[1:])
    keepref_full_mod_name = '.'.join(["_upstream"] + mp[1:])
    adapt_mod_name = mp[-1]
    parent_full_mod_name = '.'.join(mp[:-1])
    try:
        mod = __import__(adapt_full_mod_name,globals(),locals(),
('*',))
    except ImportError:
        pass
    else:
        sys.modules[keepref_full_mod_name] = sys.modules
[base_full_mod_name]
        sys.modules[base_full_mod_name] = mod
        setattr(sys.modules[parent_full_mod_name],adapt_mod_name,mod)


Most of the modules in package "upstream" have a little bit of
boilerplate at the very end that looks something like this:

from . import adaptation
adaptation.adapt(__name__)


Believe it or not, this causes whoever imports the module to actually
receive a different module.  It exploits the fact that the semantics
of __import__ are, for the lack of a better word, retarded.  If you
write "from upstream import staging", staging would end up bound to
the module custom.stating (if custom.staging exists).

This allows you to subclass upsteam.staging.Box naturally, and anyone
who tries to use upstream.staging.Box will actually be using
custom.staging.Box.  (Actually not quite true, a user in the same
module would still be using Box, but that's easily work-around-able.)

The major drawback is that "from upstream.staging import Box" will
fail.  It will import upstream.staging.Box, not custom.stating.Box" if
it's the first time upstream.stating is imported.  I can live with it
since I expect the modules to be carefully imported in order before
anyone uses their contents.

A minor drawback is it only works for modules in a package, no
biggie.  It probably crashes and burns in circular import situations,
and there's almost certainly some other pitfalls.


It's probably too magical for most people, but as I said I can
tolerate a lot of magic.  I think it's ok because all adaptible
modules have boilerplate at the end cluing a user that some other
behavior might be tacked onto this module.  That was the key sticking
point for me, I could have used an import hook, but I REALLY didn't
want a user to see "import upstream.staging" and not have any clue why
they were importing custom.staging.


Carl Banks



More information about the Python-list mailing list