21

While managing several different screen sessions with vim open in many of them, in the process of trying to "organize" my sessions I somehow managed to overwrite a very important .py script with a 0Byte file.

However, I have an ipython instance open that, when running that same .py file as a module, still remembers the code that used to be there!

So did I just learn a hard lesson about backups (my last one was done by vim about a week ago, which would leave me with a lot of work to do), or is there any possible, conceivable way to retrieve the .py file from an already loaded module? I probably deserve this for being so cavalier, but I'm seriously desperate here.

Luke Davis
  • 2,066
  • 1
  • 14
  • 33
  • 1
    to retrieve what you typed in the console: `import readline; print '\n'.join([str(readline.get_history_item(i)) for i in range(readline.get_current_history_length())])` but this probably does not solve your problem – jojo Jul 29 '15 at 17:53
  • I don't think that is really what he's looking for. He knows the name of the module he loaded, he's looking to retrieve the contents of the currently loaded module. –  Jul 29 '15 at 17:55
  • If you have a pyc file still hanging around, [try this](http://stackoverflow.com/questions/5287253/is-it-possible-to-decompile-a-compiled-pyc-file-into-a-py-file)? (I think ipython produces pyc files, but not sure) – NightShadeQueen Jul 29 '15 at 18:01
  • Slater is right. What I'm worried about is what the executable script that ipython remembers even looks like, and whether whatever format it's in can be re-structured into a simple .py. I know that when I change a .py file's contents, before reloading the module, ipython throws ERRORS from the before-change script, while showing the actual CONTENTS of the after-change script. Which leads me to believe it's not just saving a readable format of the file in the background. – Luke Davis Jul 29 '15 at 18:01
  • Given you are in an Ipython session, have you tested `my_module??`... never mind, does not work if the file is gone. – jojo Jul 29 '15 at 18:02
  • @NightShadeQueen, I have a .pyc but I'm pretty sure it's for the new 0Byte file unfortunatley (it's only 104B). Will rename it and try that method anyway though. – Luke Davis Jul 29 '15 at 18:05
  • what version of python? – Padraic Cunningham Jul 29 '15 at 18:18
  • Use some utility to fish for strings in the interpreters memory. It's also worth doing this with the disk. – usr Jul 29 '15 at 22:45
  • 3
    Very important, but not enough to use source control or backups? – Kevin Jul 29 '15 at 23:42
  • @Kevin To be fair, it could be a file that's been in progress for a couple of days and has not yet been committed, but the point is still a good one. (And not committing for that long is bad if you can avoid it.) – jpmc26 Jul 30 '15 at 00:35

3 Answers3

23

As noted in comments, inspect.getsource will not work because it depends on the original file (ie, module.__file__).

Best option: check to see if there's a .pyc file (ex, foo.pyc should be beside foo.py). If there is, you can use Decompile Python 2.7 .pyc to decompile it.

The inspect modules also caches the source. You may be able to get lucky and use inspect.getsource(module), or inspect.getsourcelines(module.function) if it has been called in the past.

Otherwise you'll need to rebuild the module "manually" by inspecting the exports (ie, module.__globals__). Constants and whatnot are obvious, and for functions you can use func.func_name to get its name, func.__doc__ to get the docstring, inspect.getargspec(func) to get the arguments, and func.func_code to get details about the code: co_firstlineno will get the line number, then co_code will get the code. There's more on decompiling that here: Exploring and decompiling python bytecode

For example, to use uncompyle2:

>>> def foo():
...     print "Hello, world!"
...
>>> from StringIO import StringIO
>>> import uncompyle2
>>> out = StringIO()
>>> uncompyle2.uncompyle("2.7", foo.func_code, out=out)
>>> print out.getvalue()
print 'Hello, world!'

But, no — I'm not aware of any more straight forward method to take a module and get the source code back out.

Community
  • 1
  • 1
David Wolever
  • 130,273
  • 78
  • 311
  • 472
1

With the process still running, you can look to your namespace to find candidates to restore:

>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'readline', 'rlcompleter', 'test']

Let's peek at what we have in store for test:

>>> help(test)

Help on module test:

NAME
    test

FILE
    /Users/tfisher/code/ffi4wd/test.py

FUNCTIONS
    call_cat(cat)

DATA
    cat_name = 'commander sprinkles'

Which has cleaner output than looking at the locals inside of test:

>>> dir(test)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'call_cat', 'cat_name', 'json']

Using the inspect module, we can get the argument specifications for functions:

>>> inspect.getargspec(test.call_cat)
ArgSpec(args=['cat'], varargs=None, keywords=None, defaults=None)

or the lines inside our functions:

>>> inspect.getsourcelines(test.call_cat)
(['def call_cat(cat):\n', '    print("Hello %s" % cat)\n'], 5)

Which is reasonably close to the original:

import json

cat_name = 'commander sprinkles'

def call_cat(cat):
    print("Hello %s" % cat)

Which should work if the file gets deleted after being imported and hasn't been replaced by a file of the same name that's newer (getsourcelines uses the object cache if possible):

$ python -V
Python 2.7.10

$ ls | grep test
$
  • `inspect.getsourcelines` will not work; see comments on Ryan's answer. – David Wolever Jul 29 '15 at 18:10
  • @DavidWolever are you sure? `getsourcelines` is working on my machine with the file missing. Its mechanism may work differently than `getsource`. –  Jul 29 '15 at 18:11
  • Emphatically: http://hul.wolever.net/skitch/inspect-get-source-lines-20150729-141243.png – David Wolever Jul 29 '15 at 18:12
  • 1
    Also, `inspect` does cache the source. Try it again from a fresh session (ie, import the module, delete/truncate the file, then try to getsource). – David Wolever Jul 29 '15 at 18:15
  • 2
    I saw your edit re: `getsourcelines`. I'm leaving my answer in place as it may work for OP -- swapping out the contents of the file means re-triggering the import because the file is newer; not necessarily the case. edit: Your comment came in as I hit enter: Yeah -- I saw the cache in the source for 2.7.10. –  Jul 29 '15 at 18:16
-1

You should be able to use inspect

import inspect in your Ipython session, and, assuming you are trying to recover myModule, do:

q = inspect.getsource(myModule)

and write q to a file.

[Edit] This worked for me simulating the problem using Python 2.7.6, IPython 1.2.1

[Edit #2]

enter image description here

Ryan
  • 3,041
  • 1
  • 18
  • 31