An Introduction to Jython
Table of Contents
Jython is a version of Python written in Java and designed to run under the JVM. Because of this, it integrates closely with Java, and allows Jython programs access to all of the Java libraries. There are several reasons that Java programmers might be interested in learning Jython:
- It provides an alternative to Javascript for interacting with Java, and is in many ways more powerful than Javascript. Most would also agree it is much easier to use than Javascript.
- It is very easy to embed the Jython interpretor as a scripting or application control language in your Java applications.
- It allows more rapid prototyping, and/or "proof of concept" applications than is possible in Java.
With this very short and brief introduction, lets jump right into some code. In the spirit of programming books, we will start with the very simple "Hello World!" program as it would be written in Jython:
print "Hello, world!"
Of course, the first thing you are likely to notice about this complete Jython program, is how simple it is. Unlike regular Java, Jython does not force us to use object oriented programming if we don't want to. We don't have to enclose our program in a class, or even in a function. In reality, this program is enclosed in a class and also in a function to satisfy the requirements of Java. However, Jython does that behind the scenes so we don't have to worry about it. Now lets look at a slightly more advanced example:
file = open("Test.txt", "r") for line in file.readlines(): print line file.close()
There are several significant aspects of Jython to point out here. The first is that it is very loosely typed. We don't have to declare the type of the variable that holds the file object before we can assign to it.
The second is that it demonstrates the use of Jython's for loop, which is different than the for loop you are used to in Java. In Jython, a for loop performs the body of the loop once for each item in a set of items it is given, which is usually a list (array) or tuple. (A tuple is basically an immutable array.) In this case, we have told Jython to place all of the lines in the text file into a list of strings, and then perform the body of the loop once for each string in the list, which of course, simply prints each line of the text file out to the console. This combination of opening the file and iterating over its contents shows how easy and quick it is to create and use objects for common tasks in Jython.
If you want a more traditional for loop in Jython, you can use the range() function, which will give the for loop a range of numbers to iterate over. For example:
for i in range(10):
will repeat the loop from 0 to 9, with i taking on the values of 0 through 9 with each repetition.
The final thing to note here is that in Jython, whitespace is significant. However, before you panic and have flashbacks of FORTRAN, note that Jython's use of whitespace makes a lot of sense. In the example, the body of the for loop is delimited by the start and end of indentation. Jython code blocks are always delimited by indentation rather than braces. So basically, just indent code as you normally would, and Jython's use of whitespace being significant will seem very natural.
Java objects and methods in Jython
Of course, as I mentioned at the start of this article, one of Jython's biggest strengths is its ability to use Java libraries. So lets start with a simple example using Swing components:
from javax.swing import * frame = JFrame("Hello Jython") label = JLabel("Hello Jython!", JLabel.CENTER) frame.add(label) frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) frame.setSize(300, 300) frame.show()
Most of this code will be immediately familiar to Java programmers. The only difference being the syntax differences of not using the new keyword, and of not requiring terminating semicolons. Once again, we also did not have to enclose our program in a class or main method.
Jython import statements
Jython import statements do require a bit more discussion here though since there are several ways we can do it. In the above example, we used thefrom syntax to import everything from javax.swing using the * wildcard, and we did it in such a way that we can use unqualified names. This is often considered bad style since it pollutes the global namespace. Another way we can do this is to import only what we need. For example, we could have used the following import statement in the above example:
from javax.swing import JFrame, JLabel
Here we can still use the unqualified names when we want to instantiate these classes, but we have also greatly reduced the risk of a name collision by only importing what we needed.
Another way we can import into Jython is by using import without from. For example:
import javax.swing
If we do it this way, we have to use the qualified names. For example, we would need to to type:
label = javax.swing.JLabel("Hello, Jython", javax.swing.JLabel.CENTER)
Although it is more typing, we have avoided polluting the global namespace.
Event handling
Basic event handling in Jython is also significantly easier than in Java. The following example replaces the JLabel with a JButton to show how easy it is:
from javax.swing import * def hello(event): print "Hello, world!" frame = JFrame("Hello Jython") button = JButton("Hello", actionPerformed = hello) frame.add(button) frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) frame.setSize(300, 300) frame.show()
Once again, this is simple enough that it doesn't require much explanation. We have simply created a function, and bound it to the button using the actionPerformed parameter. And we did it without having to create any listeners or anonymous inner classes. Jython handles the messy details of creating listeners and such for us. Our function receives one argument when it is called that contains a long list of properties regarding the event. If you want to see this list of properties, simply add "print event" to the hello function.
There is one problem that becomes apparent here. We do not have a way to pass any arguments to the event handler. We can't simply do something like "hello(5)". If you try, the AWT event queue will throw a TypeError exception. So what can we do if we need to pass arguments to the event handler? We can use a lambda. For those of you with some experience in a functional programming language such as LISP, you may remember that a lambda is an anonymous function. (The name is taken from the same term in calculus, which also means an anonymous function.) What we need to do here, is wrap the real event handler call in a lambda so that the AWT event queue only gets one argument. It looks like this:
button = JButton("Hello", actionPerformed = lambda x, param=5: hello(param))
This syntax may look a bit strange at first, but what we've done is pretty simple. The lambda has two parameters: x being the hello function, and param being the value 5. When actionPerformed is called, the lambda returns an anonymous function that invokes hello with the argument of 5. However, since the AWT event queue only got a single argument (the anonymous function returned by the lambda), it is happy and doesn't complain about a TypeError anymore. If this still seems a little confusing, consider that in Jython, instead of using the normal def funcName() syntax, we could, if we wanted to, define a function like this:
adder = lambda x, y: x + y
It's unorthodox, but it works. adder is now a reference to an anonymous function that takes two arguments and adds them together. We would call it like any other function, adder(5, 5), for example.
As an interesting side note, our adder function is automatically polymorphic because of the overloaded behavior of Jython's + operator. All three of the following adder calls are legal and will have the expected behavior:
adder(5, 5) adder(5.75, 3.15) adder("String1", "String2")
Keep this in mind as you write Jython functions and methods. Most of the ones you write will automatically be polymorphic because most of the Jython statements you will build them from are polymorphic in nature.
Overloaded Java methods
One other issue regarding calling Java methods before we move on. Because Jython is loosely typed, we can sometimes run into problems when calling overloaded Java methods. How do we determine which method to call? In this case, we can specifically tell Jython how it should treat the value we are using by passing a Java type. For example, if we have an overloaded Java method that can take either a float or a double as a parameter, we can tell Jython how to treat the passed value with something like the following:
from java.lang import Float, Double foo(Float(5.5)) foo(Double(5.5))
Basically, you can think of it is casting the value to the correct type so that Jython knows which version of the overloaded Java method to call.
Classes in Jython
Now that we have seen the basics of using Jython, lets look into Jython's support for object oriented programming. Creating a class in Jython is quite simple. Lets look at an example.
class Hello: def __init__(self, name="John Doe"): self.name = name def greeting(self): print "Hello, %s" % self.name jane = Hello("Jane Doe") joe = Hello("Joe") default = Hello() jane.greeting() joe.greeting() default.greeting()
As you've probably figured out, that __init__ method is the constructor, and we can pass it default arguments for any arguments the user does not supply, as with the name argument. Note also that we can supply named parameters when we instantiate a class or call a method. For example, we could write:
jane = Hello(name = "Jane Doe")
If we do things this way, it doesn't matter what order we supply the arguments in.
So what is that self term? It is a reference to the current instance of the class. In other words, it is the equivalent of the Java this reference. Calling itself is just a Jython convention. You can use any name you want. You could call it this if you want to. Unlike Java, Jython requires us to prefix any instance variable with self. Failing to provide the self prefix will usually result in an exception being thrown. Note also that when creating a bound method, the first parameter to the method is always self. Also, if you wish to call a bound method from within the class that it is a member of, you must prefix it with self. So for example, if we want to call our greeting() method from within the class, we would write:
self.greeting()
So what do we do if we want a static variable? The following example modifies the above example slightly and uses a static variable:
class Hello: name = "" def __init__(self, name="John Doe"): Hello.name = name def greeting(self): print "Hello, %s" % Hello.name jane = Hello("Jane Doe") joe = Hello("Joe") default = Hello() jane.greeting() joe.greeting() default.greeting()
We simply prefix the variable with the class name instead of with self. In the above example, all of the greeting() method calls will print John Doe because that is the last name that was assigned when the class was instantiated, thus giving us the expected behavior for a static variable.
Public vs. private variables
In Jython, variables and class methods can either be public or private. There is no "protected" level of visibility as there is in Java. The variables and methods in our previous examples were public. If we want to make them private, we can prefix them with a double underscore. For example, the following variable would be private:
self.__name = name
Subclassing and inheritance
Now lets take a look at a subclassing and inheritance example in Jython. Admittedly, this example is very contrived, but it points out the basic concepts and features of subclassing in Jython.
class Hello: def __init__(self): self.hello = "Hello" class Goodbye: def __init__(self): self.goodbye = "Goodbye" class Greeter(Hello, Goodbye): def __init__(self): Hello.__init__(self) Goodbye.__init__(self) def printer(self): print self.hello print self.goodbye x = Greeter() x.printer()
Uh oh... You guessed right. Yes, Jython allows multiple inheritance, as we demonstrate in the above example. The magic that Jython does behind the scenes to allow us to do this is beyond the scope of this article, and of course, no one says you have to use multiple inheritance. However, this example demonstrates how to use inheritance in Jython classes. Our Greeter class inherits from both Hello, and Goodbye. As you probably guessed, our Greeter constructor calls both superclass constructors using the ClassName.__init__ syntax. We use this same ClassName.method syntax for any method we want to call in the superclass.
Now that we have shown that Jython does allow us to use multiple inheritance, lets see an example of how Java programmers might want to use it.
Simulating interfaces with multiple inheritance
The following example shows how we can simulate an interface using multiple inheritance in Jython:
class MyInterface: def __init__(self): if not hasattr(self, "myMethod"): raise AttributeError("MyClass must implement myMethod") class MyClass(MyInterface): def __init__(self): MyInterface.__init__(self)
In this example, we've introduced a few new concepts. The first is the hasattr Jython built in function. Here we have used it to simulate an interface by using multiple inheritance. If MyClass does not contain the method myMethod, then MyClass will throw an AttributeError exception when we try to instantiate it because of the inherited behavior from the MyInterface constructor. This example also shows the basics of throwing our own exceptions in Jython.
So how is this interface simulation useful? Jython has another built in method called isinstance that we can use to determine whether a given object is an instance of any given class. Although type checking is generally considered bad in Jython since we would prefer our objects to be as polymorphic as possible because of the loose typing, sometimes it is necessary. Using the hasattr and isinstance built in functions, we can set up an interface style relationship that requires a class to contain a certain method or methods, and allows us to treat that class as an object of the interface type provided it has those methods. If it does not have those methods, we will get an AttributeError exception when we try to instantiate it.
Notes on static final classes
One final note. In Java, you are used to the idea of static classes to create helper functions, or utility methods. Although it is possible to create static classes in Jython, it should almost never be done and is almost always unnecessary. Because Jython does not require us to use object oriented programming, utility "classes" can simply be implemented as modules instead. Here is an example:
# Comment: myMath "utility" class def square(x): return x * x def cube(x): return x * x * x
Later, if we want to call these utility functions from another module, we can simply do it like this:
import myMath x = myMath.square(5) y = myMath.cube(5)
As you can see, classes are not necessary here for these utility functions, and Python's import statement allows us to import the functions in their own namespace so that we do not pollute the global namespace despite the fact that we are not using a class.
Conclusion
This article has only provided a simple introduction to Jython, and how it can be a powerful tool in a Java programmer's arsenal. For those who want to learn more, you can use any normal Python tutorial to learn Jython. The Jython specific aspects such as using Java libraries will come naturally since it is almost identical to using those libraries in Java itself.
Discuss this article with Mike
No comments:
Post a Comment