|
|
|
Advertisement
|
I recently spent a month teaching advanced C++ and introductory Python at the world's fourth largest software company. The C++ students were sharp, experienced developers. The Python students were talented testers but fairly new to development. Naturally I couldn't keep myself from comparing these two important languages, as well as the two teaching experiences overall.
This was the first time I taught Python to non-programmers—my usual
audience is my senior Analysis of Programming Languages class. It is also
the first time I ever had students thank me as a class for each day's
experience; they sincerely thanked me after each of the eight sessions,
and even applauded on the last day. While I'd like to think that they
were applauding me, I know that they were also applauding Python itself.
It is easy to learn, yes, but it is also very powerful. Lists, tuples,
and dictionaries are built-in abstractions, and the syntax is so clean it
squeaks. On day one we were doing what it takes a std::map
to do in C++ (and remember these were non-programmers). To show Python's
object-oriented power, here's a nifty little example I got from Kent
Johnson on one of the python.org mailing lists�
Like Smalltalk, Python supports class methods, which differ from
static methods in that the exact dynamic type of the object being
operated on is an implicit parameter to the method (as a type
object). The following example keeps a separate per-class object
counter for every class in a hierarchy (in this case, a Shape
hierarchy):
class Shape(object):
_count = 0 # A shared value for Shape classes with no current objects
@classmethod
def _incr(cls):
cls._count += 1 # Create/update class attribute
@classmethod
def showCount(cls):
print 'Class %s has count = %s' % (cls.__name__, cls._count)
def __init__(self): # A constructor
self._incr()
class Point(Shape): pass # An empty subclass of Shape
class Line(Shape): pass # Ditto
This requires some perusing if you're new to Python. Python
requires indentation to show logical subordination (whereas good
programmers of other languages indent by convention), so you can
readily see that the class Shape has three methods,
_incr, showCount, and __init__.
The latter is the special name used for constructors. The other two
methods are class methods (indicated by the @classmethod
prefix), so their parameter is the unique class object for the
instance on which they are invoked. (The term "class object" here refers
to a unique, bona fide object that describes a class type, similar to, but
more robust than, std::type_info objects in C++.) The method
named _incr is only called by the constructor.
Now consider what happens when the following lines execute:
p1 = Point() p2 = Point() p3 = Point() Point.showCount() Line.showCount() x = Line() Line.showCount()The variable
p1 is bound to a new instance of
Point, whose constructor calls self._
incr(Point). (The implicit parameter I have named
self is just like this in C++, except it must
appear explicitly in the method's parameter list.) Since _incr
is a class method, it implicitly receives the class object for
Point in its cls parameter, p1
being bound to a Point object. The statement
cls._count += 1is equivalent in this case to
Point._count = Point._count + 1The first time
_incr executes on behalf of the Point
class object, the attribute Point._count doesn't exist, so the
expression Point._count
on the right-hand side of the assignment accesses Shape._count (which
is 0) by inheritance. The assignment then creates a new
attribute named _count inside the Point class
and initializes it to 1. At this point, Point._count is 1,
and Line._count doesn't yet exist (all attributes are bound
at runtime in Python). When p2 is created,
Point._count already exists, so it is incremented. Calling
Line.showCount() the first time refers to
cls._count, which is really Line._count, but no
such attribute exists yet in the Line class, so the
_count attribute in Line's base class,
Shape (still 0!), is used. When the Line variable named
x is created a few lines down, Line._count is
created and initialized to 1, just as Point._count was
earlier. The final output of this code is:
Class Point has count = 3 Class Line has count = 0 Class Line has count = 1
After three years of study, I have concluded that Python is about as simple as a full-powered object-oriented language can get. My inner programmer just loves it. In a recent interview Scott Meyers was asked which language he thought would be ideal for introducing programming to novices. He replied:
"� a first language should foster a feeling of power and accomplishment out of the box — it should get people excited about the limitless things that can be accomplished in software. Among other things, such a language would offer an extensive and powerful library�" [1]
I know of no language to which these words apply more than Python.
A C++ implementation of this counting idea is none other than the canonical example for a Curiously Recurring Template Pattern (aka a Self-parameterized Base Class), and goes like this:
// A base class that provides counting
template<class T> class Counted {
static int count;
public:
Counted() {
++count;
}
Counted(const Counted<T>&) {
++count;
}
~Counted() {
--count;
}
static int getCount() {
return count;
}
};
template<class T> int Counted<T>::count = 0;
// Curious class definitions
class CountedClass : public Counted<CountedClass> {};
class CountedClass2 : public Counted<CountedClass2> {};
Both CountedClass and CountedClass2 have
inherited their own copy of a variable named count, because
their base classes are different; e.g., CountedClass inherits
from Counted<CountedClass>. This seems circular at
first, but it works because the size of Counted<T> does
not depend on T. The following test driver verifies that
things work as expected:
#include <iostream>
using namespace std;
int main() {
CountedClass a;
cout << CountedClass::getCount() << endl; // 1
CountedClass b;
cout << CountedClass::getCount() << endl; // 2
CountedClass2 c;
cout << CountedClass2::getCount() << endl; // 1
}
Which version do you find easier to grok? If you're not used to
dynamically-typed languages, you may prefer the C++ version, but I think
the Python version has a more out-of-the-box feel. (C++ novices don't
learn templates, let alone CRTP, in their first week.)
|
Sponsored Links
|