I’m now busy reading the great book Learning Python by Mark Lutz. I must say it reminds the C++ bible: The C++ Programming Language by Bjarne Stroustrup. The thing I like about this book – a very thorough content, covering each and every major part of Python programming language. Being a C++ developer, I like to know what happens behind the scence when you write code. And this “Learning Python”, though being much more steadier than, for example, another great book Dive Into Python, covers language in much more detail, leaving behind domain specific information like Web-services, XML handling, etc.. That’s very nice 🙂

While reading a chapter on functions basics, it came to my mind, that Python polymorphism reminds the so-called “static polymorphism” found in C++ templates. The idioms behind both are very much the same. In both cases you define a function that accepts arguments, regardless of their type until they follow some predefined protocol. This way of programming OOP is different from common polymorphism found in static-typed languages.

Usually if you want to make use of polymorphism in C++, you write something like this:

struct IBase
{
    virtual void func() = 0;
    virtual ~IBase() = 0 {}
}
struct SomeClass : public IBase
{
    virtual void func() { std::cout << "Hello there!\n"}
}
[...] //Skip
void someFancyFunction(IBase* arg)
{
    arg->func();
}
[...] //Skip
int main()
{
    IBase* pBase = new SomeClass;
    someFancyFunction(pBase);
    [...] //Skip
}

Here, the function someFancyFunction can only accept arguments that undoubtedly follow the IBase interface. If you try to pass some param unrelated to IBase, the compiler will report an error. This kind of polymorphism is powerful and widely-used, but it encourages a lot of boilerplate code and the code-base tends to extend hugely. It also puts a set of constraints on a developer, preventing from code from flexibility.

On other hand, there is what’s called “static polymorphism” in C++, and it’s the main tool of OOP in Python. Here’s the code example in C++:

template<typename Type>
void fancyFunction(Type& t)
{
    t.func();
    t++;
}

Now, the fancyFunction will happily accept any argument as long as its underlying type has the func method and also provides the post-increment operator. This construction, though less readable, gives much more flexibility to the developer. To use the function, one doesn’t need to derive a new type from some basic interface and implement all of its abstract methods. The only obligation is to define methods used inside fancyFunction. That’s a great leap forward and generally a superb feature of C++.

As, for Python, here polymorphism follows the C++ “static polymorphism” idiom, but it allows to achieve the same goals with less effort. Naturally, functions in Python don’t allow to declare types for arguments, so there’s no need to use cumbersome template<> constructions. In fact, all functions and operations in Python follow static polymorphism – actual receiver of a function call or operand is detected at the point of use in runtime.

To sum up, here’s the list of differences and likenesses of static polymorphism in Python and C++:

  • Both are very similar in behaviour – concrete data types are not specified in function definition, and any type can be accepted until it follows the protocol used inside the function
  • Both are very powerful indeed: C++ templates can be applied for such great things as Curiously Recurring Pattern. On other hand Python classes be defined dynamically in runtime and can be compound in metaclasses.
  • Python and C++ handle dynamic polymorphism differently – in C++ each use of a template with different kind of type actually generates a parallel family of functions and correctness of its use is checked at compile time, saving cost of dispatching via virtual table, but increasing amount of code. In Python actual types are resolved in runtime much like what is done when virtual functions are used in usual polymorphism in C++ – interpreter checks whether the typed passed to function contains needed method or data member, influencing the execution time, but leaving the code size as is.

These parallels between languages  helped me to understand clearly what is polymorphism in Python and how it should be used. As a general rule, you don’t check types in Python methods – testing input arguments is considered lame and redundant here.

P.S. Here’s a little code-snippet that I consider particularly exciting:

def func(x):
   class D(x):
      def foo(self):
         print "Oy!"

class C(object):
    def foo(self):
       print "Hello!"

z = func(C)
z.foo() # prints "Oy!"