4

Because I am an idiot, I deleted some python files and failed to back them up. Before doing this I opened the python interpreter (ie, ran python) and then used the command import myfile.py.

EDIT: I actually used the command import myfile, which, apparently, is worse.

Is there any way I can recover the .pyc (or better the .py, but that seems impossible) file from the python interpreter session I have open?

tanderson11
  • 97
  • 1
  • 9
  • I think you can even `import inspect` and do `inspect.getsource(myfile)` – dustyrockpyle Jul 28 '14 at 17:38
  • You mean you used `import myfile`, not `import myfile.py`, right? – abarnert Jul 28 '14 at 17:38
  • @abarnert. Yes that is what I mean. – tanderson11 Jul 28 '14 at 17:41
  • What platform are you on? The details are pretty different from Windows vs. *nix. – abarnert Jul 28 '14 at 17:41
  • @dustyrockpyle Unfortunately that produced an error as I addressed in another answer. – tanderson11 Jul 28 '14 at 17:41
  • @abarnert I'm on linux (ubuntu if it matters). – tanderson11 Jul 28 '14 at 17:42
  • @tanderson11: OK, on *nix, if the `.py` file has been loaded it will be available even if you unlike the file, but if only the `.pyc` has been loaded, the `.py` file may be gone forever. So you need a way to dump the `.pyc`. IIRC, that's not simple but doable in recent Python, but requires a lot of hackery in older versions, so… which version are you using? – abarnert Jul 28 '14 at 17:44
  • @abarnert I'm using python 2.7.6 (thanks for all the help by the way!) Also, no idea if it matters, but when I run the loaded commands and they produce an error, it does produce output like, "File "request_io.py", line 112, in generate_dict IndexError: string index out of range" – tanderson11 Jul 28 '14 at 17:45
  • Damn, I don't remember how to find the .pyc pathname pre-`importlib`, or how to get at the module's compiled code. Maybe someone else does? If you want to figure it out yourself, the first step to the second half of the problem is to `import dis` then `dis.dis(myfile)`. If that prints a bunch of bytecode that you don't understand, then it's possible to get at the compiled code, and the source to `dis.py` (which is linked from the `dis` module docs) should show how to do it… – abarnert Jul 28 '14 at 17:51

2 Answers2

4

The byte-code decompiler uncompyle2 can decompile Python 2.x classes, methods, functions and code to source code (note: via Reassembling Python bytecode to the original code?).

This will work well enough for functions:

from StringIO import StringIO
from uncompyle2 import uncompyle
from inspect import *

def decompile_function(f, indent=''):
    s = StringIO()
    uncompyle(2.7, f.func_code, s)
    return '%sdef %s%s:\n%s    %s' % (
        indent,
        f.func_name,
        inspect.formatargspec(*inspect.getargspec(f)),
        indent,
        ('\n    ' + indent).join(''.join(s.buflist).split('\n')))

Unfortunately because classes are already executed it won't be able to recover their structure; you'd need to decompile the methods individually and hope that that's enough:

def decompile_class(c):
    return 'class %s(%s):\n' % (
        c.__name__,
        ','.join(b.__module__ + '.' + b.__name__ for b in c.__bases__)) + \
        '\n'.join(decompile_function(m.im_func, '    ')
                  for n, m in inspect.getmembers(c) if inspect.ismethod(m))

Full solution:

def decompile_module(mod):
    return '\n\n'.join(decompile_function(m) if isfunction(m) else
        decompile_class(m) if isclass(m) else
        '# UNKNOWN: ' + repr((n, m))
        for n, m in inspect.getmembers(mod) if inspect.getmodule(m) is mod)
Community
  • 1
  • 1
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • Actually, `inspect.getmembers(myfile)` should work just as well for getting all of the members. – abarnert Jul 28 '14 at 17:53
  • But this is a clever idea: rather than trying to recover the compiled code and figure out how to dump it to a `.pyc`, turn it back to source code and dump it to a functionally-equivalent `.py`. Nice solution! – abarnert Jul 28 '14 at 17:54
  • on my computer this failed unless I specified the file: `uncompyle.uncompyle(2.7, myfile.myfunc.func_code, open('myfile-1.py','w'))` or `uncompyle.uncompyle(2.7, myfile.myfunc.func_code, sys.stdout)` – loopbackbee Jul 28 '14 at 18:08
  • @goncalopp: It looks like you're using `uncompyle` instead of `uncompyle2`. AFAIK they're both forks of the same project, so using them should be pretty similar… but if someone suggests one and you use the other and it doesn't work, you should try the one he suggested. – abarnert Jul 28 '14 at 19:31
  • @abarnert I did try uncompyle2, and it has the same behaviour. StringIO is a great workaround, though – loopbackbee Jul 29 '14 at 09:48
  • @goncalopp: Just to make sure you're giving credit where it's due, the `StringIO` workaround is ecatmur's idea, not mine, just like the main cleverness of using `uncompyle2` to get something you can usefully save in the first place. I'd give him more upvotes if I could. :) – abarnert Jul 29 '14 at 17:39
1

inspect.getsource supports dumping modules, so you can simply

import inspect
inspect.getsource(myfile)

As that doesn't seem to work, you should at least be able to get the disassembled (".pyc) code with

import dis
dis.dis(myfile)
loopbackbee
  • 21,962
  • 10
  • 62
  • 97
  • Produces the error: "IOError: source code not available." – tanderson11 Jul 28 '14 at 17:40
  • In the case where the module was imported by `.pyc` not `.py`, and the `.py` file has been deleted, this won't work. – abarnert Jul 28 '14 at 17:41
  • @goncalopp Alright, with that I can access the disassembled code. Obviously from here I can cobble together a working version of my code more quickly. Ideally, I would be able to recover python code from this, but that's a lossy process I imagine. Any thoughts? – tanderson11 Jul 28 '14 at 17:53