January 13, 2023

Subclass Like a Boss

I had this idea for a project I could do, but I thought “maybe I should do this other thing first to get to grips with the API”. I was doing something for that project, and got distracted doing a simpler project to learn about the technologies and tools involved. I got distracted from that project by some weird behaviour of a class in the python standard library (I’ll post about that later). And I got distracted from that distraction by trying to find convenient ways to explore the code of python objects so as to make subclassing them easier. And this post is about this distraction to a distraction to a distraction. With any luck, I will eventually post about all of these things, but I’m not naming them in case I never get around to doing them.

So you want to subclass an object in python. Ideally, if you’re going to make a subclass, you want to see the code for the original object to use as a scaffold when you start over-riding things. You could dig around to try to find where that object is defined, but that’s hit-and-miss, and kind of awkward if you only want to override one or two methods. Particularly if that object is inheriting some methods from its parents etc etc.

Python has a great module called inspect which basically gives you a bunch of tools to make this sort of thing way easier. In my fiddling around, I found the following wrapper around inspect kind of useful.

import inspect
import pyperclip


class MethodInspector:
    def __init__(self, obj):
        self.obj = obj
        self.methods = dict(inspect.getmembers(obj))

    def _get_source(self, method_name):
        return inspect.getsource(self.methods[method_name])

    def _get_parents(self):
        return type(self.obj).__bases__

    def print_source(self, method_name):
        print(self._get_source(method_name))
        
    def copy_source(self, method_name):
        pyperclip.copy(self._get_source(method_name))

    def print_parents(self):
        print("\n".join([x.__name__ for x in self._get_parents()]))

    def show_methods(self):
        print("\n".join(self.methods.keys()))

What does it do? You instantiate a MethodInspector by passing it an instance of the object you want to explore (the object you want to subclass in my case). Then show_methods() will print out a list of the methods defined for that object. This includes all methods it inherits from its parents.

Then we have print_source(method_name) which prints the source code for the method called method_name. (You pass in the method name as a string). You can use copy_source(method_name) to copy the source for a method to the clipboard, which you can then paste into your subclass. This makes use of the pyperclip module which gives you a simple way to have python interact with your clipboard.

Finally, print_parents() prints out the parent classes of the object. This doesn’t use the inspect module, but it’s also a useful thing to look at on occasion.

The limitation with this at the moment is that if you hit a method defined in the built in core of the language (for example, dict.__setitem__) then inspect will error out. But if that happens, this probably means that the object is defined in C rather than in python itself. For example, here’s the github page for the defintion of the dictionary object.

© Seamus Bradley 2021–3

Powered by Hugo & Kiss.