The Static Method Thing
Phillip J. Eby has a recent weblog entry, Python is not Java, wherein he points out a few aspects of Python that are notably different from Java but that are similar enough that Java coders working in Python mistake them for their Java counterparts.
He touches on a few of the characteristics that led me to make Python my general purpose language of choice, a title that had belonged to Java as recently as a year ago. I thought it might be useful to explain these characteristics without assuming the reader has a whole lot of Python experience but that they do have significant Java experience. This would have helped me immensely in determining whether Python was right for me much sooner and I hope it can be of use to someone else.
I’m planning on covering each of the items Phillip points out in his article over the next month and if that works out, I may extend the series to a few other things that may be useful.
This is not a Python is better than Java
thing. Instead of
bashing aspects of Java’s language and library that are
obviously valued by the Java community, or insulting the intelligence
of Java coders, or using any number of other techniques you
might find in Every Language War Ever, Phillip is simply asking
Java programmers who may be dabbling in Python to take a closer look
at some of the features that make Python unique and attractive instead
of attempting to force-fit concepts from Java. If you base your
expectations of Python on Java concepts, you are likely going to have
a bad experience with Python.
Continue on for the first part of the series, which talks about some of the differences between Python class methods / class variables and Java static methods / static variables.
Python Class Methods are not Java Static Methods
I’ll start from the top with the comment on static variables and methods in Java and why the equivalent Python code looks a bit different:
A static method in Java does not translate to a Python classmethod. Oh sure, it results in more or less the same effect, but the goal of a classmethod is actually to do something that’s usually not even possible in Java (like inheriting a non-default constructor).
We’ll get into the inheriting a non-default constructor
bit in
a second. For now, let’s take a look at a static variable and method
in Java:
public class X {
public static String message = "Hello";
public static void sayHello() {
System.out.println(X.message);
}
}
Calling this method from another class looks like this:
import X
public class Main {
public static void main(String[] args) {
X.sayHello();
}
}
Now, if you’re teaching someone to program and using Java to
illustrate, this situation kind of sucks. You’ve introduced classes,
the static
modifier, and access modifiers - all before you can even
start to talk about the basic concept of a function (led alone a
method) or writing output to the screen. (Note: this makes Java only
mildly less shitty than C++ for teaching programming because at least
you don’t have to deal with pointers and operator overloading: cout
<< "Hello"
.)
Whether Java’s constraint of requiring everything to be written within a class was a good idea is debatable (and it has been debated to death). On the one hand, it forces programmers to think about programming using OOP concepts, which generally helps ensure that code is somewhat reusable (so the theory goes). On the other hand, it forces programmers to think about programming using OOP concepts, even when they may be overkill. This leads to code like the example above that has a lot of extra scenery to distract you from the intent.
The idiomatic translation of a Java static method is usually a module-level function, not a classmethod or staticmethod. (And static final fields should translate to module-level constants.)
Here’s the idiomatic translation to Python of the Java example
above. The following is in a module file named X.py
:
message = "Hello"
def say_hello():
print message
And calling the function from another module (in Main.py
):
import X
X.say_hello()
Now let’s look at an example of what not to do in Python:
class X:
message = "Hello"
def say_hello(cls):
print X.message
say_hello = classmethod(say_hello)
Calling the above from another module looks like this:
import X
X.X.say_hello()
Here’s why: in Python, each module
is itself an object without
having to define an explicit class. A single module in Python maps to
a single .py
file on disk. Modules may contain functions, attributes
(variables), multiple class definitions, etc. There is no equivalent
of the Python module in Java - each .java
file must have a single
top-level class that matches the filename. Imports in Java work at
the class level; imports in Python work at the module level.
This may just confuse things further but it may help for the Java inclined to think of each Python module as acting like a purely static Java class. Functions defined at the top level of a module are static methods on the class. Variables defined at the top level of a module are static members. Classes defined at the top level of a module act like public static inner classes. In other words, the idiomatic Python example maps exactly to the Java example without having to write an explicit class at all. The example of what not to do in Python would look like this if you ported it directly to Java:
public class X {
public static class X {
public static String message = "Hello";
public void sayHello() {
System.out.println(X.X.message);
}
};
}
It should now be obvious why the second form is incorrect in Python, it’s incorrect in Java!
Class Method Inheritance
So why have a Java static method like construct in Python at all? Phillip begins to answer that question in his post:
the goal of a classmethod is actually to do something that’s usually not even possible in Java (like inheriting a non-default constructor).
In a Python classmethod, the class object that the method is invoked on is passed as the first parameter to the method. This can be used for a variety of weird and arcane things but the most common use is for factory-style constructors.
Like static methods in Java, Python classmethods are inherited by subclasses. Unlike Java, it is possible for subclasses to override classmethods. Python also passes the class object that a classmethod is called on to the method as the first parameter. This gives superclasses access to the subclass’ methods (such as the constructor).
Here’s an example that provides a caching mechanism for new classes:
class Cacheable:
_cache = {}
def create_cached(cls, value):
if not cls._cache.has_key(value):
print 'cache miss'
rslt = cls(value)
cls._cache[value] = rslt
else:
print 'cache hit'
rslt = cls._cache[value]
return rslt
create_cached = classmethod(create_cached)
class X(Cacheable):
def __init__(self, value):
self.value = value
x = X.create_cached('test') # X instance created
x = X.create_cached('test2') # X instance created
x = X.create_cached('test') # X instance taken from cache
When we call X.create_cached()
, the method is called on the
Cacheable
class but the cls
argument is set to the X
class
object. This means that many classes can subclass Cacheable
and
inherit the class methods it defines (including __new__
, which is
something we won’t get into).
This technique is actually used very rarely in Python as there are usually simpler ways of accomplishing the same thing, and simplicity is highly valued in Python.
I hope this helped illuminate the difference between Python class methods/variables and Java static methods/variables. Remember, the point isn’t to convince you that Python classmethods are somehow cooler than Java static methods or vice versa, just that the two concepts are not equivalent.