chameleon chameleon - 6 months ago 14
Python Question

how to change relative import search path

I'm trying to create an auto_import function which is part of a library: the purpose of this to avoid listing

from .x import y
many times in
__init__
files, only do something this
import lib; lib.auto_import(__file__)
<- this would search for python files in that folder where the
__init__
is present and would import all stuff by exec statement (i.e.
exec('from .x import abc')
).

My problem is that, somehow the 'from' statement always tries to import .x from lib directory, even if I change the cwd to the directory where the actual
__init__
file is placed... How should I solve this? How should I change the search dir for
from .
statement?

Structure:

$ ls -R
.:
app.py lib x

./lib:
__init__.py auto_import.py

./x:
__init__.py y

./x/y:
__init__.py y.py


e.g.:
./x/y/__init__.py
contains
import lib; lib.auto_import(__file__)

auto_import is checking for files in dir of
__file__
and import them with
exec('from .{} import *')
(but this from . is always the lib folder and not the dir of
__file__
, and that is my question, how to change this to dir of
__file__

Of course the whole stuff is imported in app.py like:

import x
print(x.y)


Thanks

EDIT1: final auto_import (globals() / gns cannot be avoided )

import os, sys, inspect

def auto_import(gns):
current_frame = inspect.currentframe()
caller_frame = inspect.getouterframes(current_frame)[1]
src_file = caller_frame[1]
for item in os.listdir(os.path.dirname(src_file)):
item = item.split('.py')[0]

if item in ['__init__', '__pycache__']:
continue

gns.update(__import__(item, gns, locals(), ['*'], 1).__dict__)

Answer

The problem of your approach is that auto_import is defined in lib/auto_import.py so the context for exec('from .x import *') is always lib/. Even though you manage to fix the path problem, lib.auto_import(__file__) will not import anything to the namespace of lib.x.y, because the function locates in another module.

Use the built-in function __import__

Here is the auto_import script:

myimporter.py

# myimporter.py
def __import_siblings__(gns, lns={}):
  for name in find_sibling_names(gns['__file__']):
    gns.update(__import__(name, gns,lns,['*'],1).__dict__)

import re,os
def find_sibling_names(filename):
  pyfp = re.compile(r'([a-zA-Z]\w*)\.py$')
  files = (pyfp.match(f) for f in os.listdir(os.path.dirname(filename)))
  return set(f.group(1) for f in files if f)

Inside your lib/x/y/__init__.py

#lib/x/y/__init__.py
from myimporter import __import_siblings__
__import_siblings__(globals(),locals())

Let's say you have a dummy module that need to be imported to y:

#lib/x/y/dummy.py
def hello():
  print 'hello'

Test it:

import x.y
x.y.hello()

Please be aware that from lib import * is usually a bad habit because of namespace pollution. Use it with caution.

Comments