7

Usually, if you want to get the source of an object, you can get it via the inspect module:

import inspect
inspect.getsource(MyObject)

However, in a Jupyter notebook, this doesn't work:

import inspect

class Foo:
    def __init__(self, info):
        self.info = info

a = Foo("hi")

inspect.getsource(a)

Throws the error:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-14-048b6f0c2e9b> in <module>()
      7 a = Foo("hi")
      8 
----> 9 inspect.getsource(a)

/usr/lib/python3.6/inspect.py in getsource(object)
    963     or code object.  The source code is returned as a single string.  An
    964     OSError is raised if the source code cannot be retrieved."""
--> 965     lines, lnum = getsourcelines(object)
    966     return ''.join(lines)
    967 

/usr/lib/python3.6/inspect.py in getsourcelines(object)
    950     raised if the source code cannot be retrieved."""
    951     object = unwrap(object)
--> 952     lines, lnum = findsource(object)
    953 
    954     if ismodule(object):

/usr/lib/python3.6/inspect.py in findsource(object)
    763     is raised if the source code cannot be retrieved."""
    764 
--> 765     file = getsourcefile(object)
    766     if file:
    767         # Invalidate cache if needed.

/usr/lib/python3.6/inspect.py in getsourcefile(object)
    679     Return None if no way can be identified to get the source.
    680     """
--> 681     filename = getfile(object)
    682     all_bytecode_suffixes = importlib.machinery.DEBUG_BYTECODE_SUFFIXES[:]
    683     all_bytecode_suffixes += importlib.machinery.OPTIMIZED_BYTECODE_SUFFIXES[:]

/usr/lib/python3.6/inspect.py in getfile(object)
    661         return object.co_filename
    662     raise TypeError('{!r} is not a module, class, method, '
--> 663                     'function, traceback, frame, or code object'.format(object))
    664 
    665 def getmodulename(path):

TypeError: <__main__.Foo object at 0x7fb9130ee518> is not a module, class, method, function, traceback, frame, or code object

If I try to find the source of Foo (using inspect.getsource(Foo)), I get:

TypeError: <module '__main__'> is a built-in class

How do I get the source of a class defined in a Jupyter notebook?

Andrew Spott
  • 3,457
  • 8
  • 33
  • 59

2 Answers2

8

I found a "hacky way" to get the source code of a class in a Jupyter Notebook.

Assume in a cell you have:

class MyClass:
    test = 2
    
    def __init__(self):
        self.L = 5
    
    def test(self, x):
        return True
    
    @classmethod
    def forward(cls, x):
        return x

Then you can extract the code using:

import inspect
from IPython.core.magics.code import extract_symbols

obj = MyClass
cell_code = "".join(inspect.linecache.getlines(new_getfile(obj)))
class_code = extract_symbols(cell_code, obj.__name__)[0][0]
print(class_code)

with new_getfile defined from here:

import inspect, sys

def new_getfile(object, _old_getfile=inspect.getfile):
    if not inspect.isclass(object):
        return _old_getfile(object)
    
    # Lookup by parent module (as in current inspect)
    if hasattr(object, '__module__'):
        object_ = sys.modules.get(object.__module__)
        if hasattr(object_, '__file__'):
            return object_.__file__
    
    # If parent module is __main__, lookup by methods (NEW)
    for name, member in inspect.getmembers(object):
        if inspect.isfunction(member) and object.__qualname__ + '.' + member.__name__ == member.__qualname__:
            return inspect.getfile(member)
    else:
        raise TypeError('Source for {!r} not found'.format(object))
inspect.getfile = new_getfile
Evann Courdier
  • 333
  • 2
  • 8
  • Worked perfectly. I have rewritten a class during the development without committing the first version to git, silly me. Then I needed the source of the first version. Luckily I had the first version class still in a running Jupyter notebook so your code got me the source back. Thanks a lot. – Paloha Nov 09 '21 at 08:47
  • I'm having `TypeError: Source for not found` – skabbit Jul 11 '22 at 09:48
  • Really cool approach! It should be noted that this answer only works for classes with methods defined in them. Also - `inspect.getfile = new_getfile` will only work in the current module's context, not in imports (for better or for worse). – Moshe Jonathan Gordon Radian Jan 18 '23 at 09:15
1

Using inspect.getsource(inspect.getfile) we can get a segment of code that deals with this:

...
if isclass(object):
    if hasattr(object, '__module__'):
        object = sys.modules.get(object.__module__)
        if hasattr(object, '__file__'):
            return object.__file__
    raise TypeError('{!r} is a built-in class'.format(object))
...

It seems in ipython or Jupyter notebook, the classes/functions defined or the __main__ module does not have a __file__ attribute associated with them so inspect wasn't able to retrieve a source file. In this case you can define the classes in a separate .py file so inspect is able to retrieve the file associated with it.

Kevin He
  • 1,210
  • 8
  • 19