[Python-Dev] PEP 302, PEP 338 and imp.getloader (was Re: a Python interface for the AST (WAS: DRAFT: python-dev...)

Nick Coghlan ncoghlan at gmail.com
Thu Nov 24 14:10:07 CET 2005


Phillip J. Eby wrote:
> This isn't hard to implement per se; setuptools for example has a 
> 'get_importer' function, and going from importer to loader is simple:

Thanks, I think I'll definitely be able to build something out of that.

> So with the above function you could do something like:
> 
> def get_loader(fullname, path):
>     for path_item in path:
>         try:
>             loader = get_importer(path_item).find_module(fullname)
>             if loader is not None:
>                 return loader
>         except ImportError:
>             continue
>     else:
>         return None
> 
> in order to implement the rest.

I think sys.meta_path needs to figure into that before digging through 
sys.path, but otherwise the concept seems basically correct.

[NickC]
>> ** I'm open to suggestions on how to deal with argv[0] and __file__. They
>> should be set to whatever __file__ would be set to by the module 
>> loader, but
>> the Importer Protocol in PEP 302 doesn't seem to expose that 
>> information. The
>> current proposal is a compromise that matches the existing behaviour 
>> of -m
>> (which supports scripts like regrtest.py) while still giving a meaningful
>> value for scripts which are not part of the normal filesystem.

[PJE]
> Ugh.  Those are tricky, no question.  I can think of several simple 
> answers for each, all of which are wrong in some way.  :)

Indeed. I tried turning to "exec co in d" and "execfile(name, d)" for 
guidance, and didn't find any real help there. The only thing they 
automatically add to the supplied dictionary is __builtins__.

The consequence is that any code executed using "exec" or "execfile" sees its 
name as being "__builtin__" because the lookup for '__name__' falls back to 
the builtin namespace.

Further, "__file__" and "__loader__" won't be set at all when using these 
functions, which may be something of a surprise for some modules (to say the 
least).

My current thinking is to actually try to distance the runpy module from 
"exec" and "execfile" significantly more than I'd originally intended. That 
way, I can explicitly focus on making it look like the item was invoked from 
the command line, without worrying about behaviour differences between this 
and the exec statement. It also means runpy can avoid the "implicitly modify 
the current namespace" behaviour that exec and execfile currently have.

The basic function runpy.run_code would look like:

   def run_code(code, init_globals=None,
                      mod_name=None, mod_file=None, mod_loader=None):
       """Executes a string of source code or a code object
          Returns the resulting top level namespace dictionary
       """
       # Handle omitted arguments
       if mod_name is None:
           mod_name = "<run>"
       if mod_file is None:
           mod_file = "<run>"
       if mod_loader is None:
           mod_loader = StandardImportLoader(".")
       # Set up the top level namespace dictionary
       run_globals = {}
       if init_globals is not None:
           run_globals.update(init_globals)
       run_globals.update(__name__ = mod_name,
                          __file__ = mod_file,
                          __loader__ = mod_loader)
       # Run it!
       exec code in run_globals
       return run_globals

Note that run_code always creates a new execution dictionary and returns it, 
in contrast to exec and execfile. This is so that naively doing:

   run_code("print 'Hi there!'", globals())

or:

   run_code("print 'Hi there!'", locals())

doesn't trash __name__, __file__ or __loader__ in the current module (which 
would be bad).

And runpy.run_module would look something like:

   def run_module(mod_name, run_globals=None, run_name=None, as_script=False)
       loader = _get_loader(mod_name) # Handle lack of imp.get_loader
       code = loader.get_code(mod_name)
       filename = _get_filename(loader, mod_name) # Handle lack of protocol
       if run_name is None:
           run_name = mod_name
       if as_script:
           sys.argv[0] = filename
       return run_code(code, run_globals, run_name, filename, loader)

Cheers,
Nick.

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia
---------------------------------------------------------------
             http://www.boredomandlaziness.org


More information about the Python-Dev mailing list