Python’s OOP: object-oriented paradox

posted: June 15, 2019

tl;dr: Python is incredibly object oriented, but you don’t have to create classes to use it...

I love a good paradox, and Python has at least two mind-bending ones. In my Abstracting away pointers post I described one: Python uses pointers everywhere, internal to the language’s implementation, and delivers all the powers and benefits of pointers without ever forcing the Python coder to explicitly manipulate a pointer. As I finish up teaching an introductory Python 101 class at meltmedia, I’ve gained a greater appreciation for a second paradox at the core of the language: Python is incredibly object-oriented, yet delivers many of the powers and benefits of object-oriented programming without, in many cases, forcing the Python coder to create classes. Beginning Python programmers may not even realize they are doing object-oriented programming, yet they are.

The students in the class are a mix of folks who are new to programming, or new to Python, or who want a refresher in the basics of the language. Knowing how almost everything in Python is an object, I was careful in the beginning to present an accurate memory model: objects that live in memory which are referenced by one or more variable names (this is one place where Python uses pointers). I see too many online Python tutorials that present a container memory model, and while that is simpler to understand, it is eventually going to cause confusion. Up front I showed how integers and strings are objects, as are True, False, and even None, and how as the value of a variable changes, the object the variable references (or points to) changes. This paid off later, as we got into more sophisticated objects and method calls.

Python is well known for being beginner-friendly as illustrated by the one line “Hello, world!” implementation:

print('Hello, world!')

which stands in stark contrast to Java’s “Hello, world!” implementation, which defines a class:

class HelloWorld {
  static public void main( String args[] ) {
    System.out.println( "Hello, world!" );

Beginner Java programmers immediately realize that they are doing object-oriented programming. Yet the beginner Python programmers are also doing object-oriented programming, because the string being printed is actually an object in memory that is being passed to the builtin print function via a reference. The difference is that the Python interpreter creates the object automatically from the builtin string (str) class, saving the Python programmer the work of doing so. Object-oriented magic is happening in Python, under the hood.

The book we’re using in the class is the second edition of Allen Downey’s excellent textbook Think Python: How to Think Like a Computer Scientist (I read the first edition years ago and reviewed it here; the second edition is even more thorough). We chose the book primarily because we wanted to convey some computer science fundamentals in addition to teaching Python. But another reason I like the book is that it doesn’t start introducing classical, explicit object-oriented programming until Chapter 15, which is the first place that the Python class keyword is used to create a custom-defined class. The first 14 chapters use Python’s built-in classes (types) and those that are available in the standard library. So the first 14 chapters focus on using classes and objects instead of defining one’s own.

We’re actually ending the introductory class at Chapter 14, because at this point the students have enough knowledge of Python to do many useful tasks. Because of the power of Python’s builtin classes/types, especially the list and dict types, and because of the classes available in Python’s extensive standard library, as well as the ever-growing world of third-party libraries (for example NumPy and pandas for data science) there is much less of a need for programmers to create custom-defined classes in Python as compared to other languages. Also, because of Python’s fully dynamic nature and the ability to access the internals of objects and classes, Python classes don’t provide some of the data isolation capabilities of other languages, which further lessens the value of creating new classes.

Even more radical than skipping custom classes 😀

In the Python programming that I’ve done, I rarely need to create a new class, although I fully realize that it depends on the problem being solved. Often Python is used to glue together other systems, in which case it usually suffices to use the standard types plus those that come from the Python libraries or SDKs provided by those other systems. In my last company, a fast-moving startup, we never got around to creating custom classes for various data objects in our data model: we just used the dict class/type, which maps nicely to JSON for web application development.

In the data science realm there is probably even less of a need to create custom classes, because so much of data science programming in Python relies upon using other libraries. Professor Downey had an interesting tweet this week in which he said he was testing the concept of skipping some of the main builtin types of Python to jump straight into the pandas data structures. That seems like a bit of a stretch to me, but perhaps I’m viewing it the same way that traditional object-oriented programmers would view skipping creating custom classes.

Pointers without manipulating pointers. Object-oriented programming without creating classes. Python has at least two intriguing paradoxes to ponder.