5

I am in the middle of refactoring a huge py module to packages - to not break existing code I moved its contents to package/__init__.py module (Adding code to __init__.py) and went on splitting it from there. I noticed at some point that in my tracebacks I get:

Traceback (most recent call last):
      File "<string>", line 656, in DoItemMenu
      File "bash\balt.py", line 2109, in PopupMenu
        link.AppendToMenu(menu,parent,*args)
      File "bash\balt.py", line 2225, in AppendToMenu
        for link in self.links: link.AppendToMenu(subMenu,window,data)
    ...

where the lines in File "<string>" correspond to the particular package/__init__.py module. Moreover PyCharm's debugger displays a "frame not available" line and does not step into the lines in the __init__.py. Why? Is it related to the import pattern?

The code is imported by a launcher class:

class UnicodeImporter(object):
    def find_module(self,fullname,path=None):
        if isinstance(fullname,unicode):
            fullname = fullname.replace(u'.',u'\\')
            exts = (u'.pyc',u'.pyo',u'.py')
        else:
            fullname = fullname.replace('.','\\')
            exts = ('.pyc','.pyo','.py')
        if os.path.exists(fullname) and os.path.isdir(fullname):
            return self
        for ext in exts:
            if os.path.exists(fullname+ext):
                return self

    def load_module(self,fullname):
        if fullname in sys.modules:
            return sys.modules[fullname]
        else:
            sys.modules[fullname] = imp.new_module(fullname)
        if isinstance(fullname,unicode):
            filename = fullname.replace(u'.',u'\\')
            ext = u'.py'
            initfile = u'__init__'
        else:
            filename = fullname.replace('.','\\')
            ext = '.py'
            initfile = '__init__'
        if os.path.exists(filename+ext):
            try:
                with open(filename+ext,'U') as fp:
                    mod = imp.load_source(fullname,filename+ext,fp)
                    sys.modules[fullname] = mod
                    mod.__loader__ = self
                    return mod
            except:
                print 'fail', filename+ext
                raise
        mod = sys.modules[fullname]
        mod.__loader__ = self
        mod.__file__ = os.path.join(os.getcwd(),filename)
        mod.__path__ = [filename]
        #init file
        initfile = os.path.join(filename,initfile+ext)
        if os.path.exists(initfile):
            with open(initfile,'U') as fp:
                code = fp.read()
            exec code in mod.__dict__
        return mod
8
  • 2
    It means that Python was told to compile that module from a string; that probably is PyCharm's fault, not Python. Commented Nov 28, 2014 at 17:38
  • How exactly are you running this code at the moment? Commented Nov 28, 2014 at 17:40
  • @MartijnPieters: here is the launcher - I am almost certain that before I copy pasted the file in basher/__init__.py it did not load a string though... Commented Nov 28, 2014 at 21:50
  • No, I meant that PyCharm is instructing the Python interpreter to run that file from a string rather than asking Python to import it as a module. I didn't say anything about the module itself doing anything. Commented Nov 28, 2014 at 21:52
  • @MartijnPieters: It happens also when I directly run the launcher - I mentioned Pycharm cause it seems to me that it's the same reason it can't step through the code in __init__.py Commented Nov 28, 2014 at 22:38

1 Answer 1

5

The code is not imported in a traditional manner; instead the launcher code uses an exec statement for loading __init__.py files.

Paring down the flow in the launcher load_module() function for a package (so not a path to a module) you get this:

# the fullname module isn't yet loaded
sys.modules[fullname] = imp.new_module(fullname)
initfile = '__init__'  # or u'__init__' if a unicode path was used

# if no .py file was found, so not a module
mod = sys.modules[fullname]
mod.__loader__ = self
mod.__file__ = os.path.join(os.getcwd(),filename)
mod.__path__ = [filename]
#init file
initfile = os.path.join(filename,initfile+ext)
if os.path.exists(initfile):
    with open(initfile,'U') as fp:
        code = fp.read()
    exec code in mod.__dict__
return mod

This creates an empty module object, loads the source manually and executes it as a string, passing in the module namespace as the globals for the executed code. The resulting code object is always going to list <string> in tracebacks:

>>> import imp
>>> mod = imp.new_module('foo.bar')
>>> mod.__file__ = r'C:\some\location\foo\bar'
>>> mod.__path__ = [r'foo\bar']
>>> exec 'raise ValueError("oops")' in mod.__dict__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
ValueError: oops

Because there is no filename associated with the code, PyCharm cannot find the original source either.

The work-around is to use the compile() function to create a code object first, and attaching a filename to that:

>>> exec compile('raise ValueError("oops")', r'C:\some\location\foo\bar\__init__.py', 'exec') in mod.__dict__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\some\location\foo\bar\__init__.py", line 1, in <module>
ValueError: oops

Note that I included __init__.py in the filename; translating that back to the launcher you'd use:

if os.path.exists(initfile):
    with open(initfile,'U') as fp:
        code = fp.read()
    exec compile(code, initfile, 'exec') in mod.__dict__
Sign up to request clarification or add additional context in comments.

3 Comments

Yep the launcher code was beyond me - will go through and digest all this and get back to you - thanks :)
Just to be sure I got it - using the compile() function will only do good in my scenario (remember I am in the middle of refactoring so these files are huge) ? Btw, I had noticed that there is no pyc file for the __init__.py, just did not mention this in the question - now I know why.
exec has to compile anyway, but by using an explicit compile() call you get to give the resulting code object a filename. It'll be helpful for all code launched by the launcher.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.