Jython excels because of its comprehensive integration of Python and Java. Most multi-level language combinations have a semantic gap between the languages that discourages truly comprehensive integration. CPython, for example, cannot use C libraries without special steps, and writing C extensions must following specific guidelines unique to CPython. Jython, on the other hand, enables the seamless usage of arbitrary Java classes in a Python environment without requiring extra steps, modification, or special treatment in writing the Java class. That’s only one side of the coin, however. To truly be comprehensive, Jython must also allow Java to seamlessly use Python. Jython meets this latter requirement, even if not fully transparently, thanks to jythonc
.
jythonc
allows you to compile Jython modules into Java *.class
files. Jythonc
can create runnable classes—those that Java can execute but still rely on Jython modules. jythonc
can also create fully self-contained and runnable applications, meaning those that Java can execute and that have all dependant modules located and compiled. Additionally, jythonc
can compile Jython classes that indistinguishably masquerade as Java classes—those that Java code can import and call.
To pique your interest, consider the advantages of being able to compile Jython applications into self-contained jar files runnable on most JVM’s. Consider the development benefits of prototyping applications in Jython and later converting portions to Java—but only as needed. Consider the ease of writing servlets, applets and beans in Jython, and having them work within Java frameworks without regard for their original language. Consider the flexibility of composing, subclassing, and extending Java and Jython classes interchangeably. These rare abilities are the functionality supplied by jythonc
.
jythonc
is a tool that generates Java code from Jython modules. jythonc
can also create jar files, track dependencies, freeze associated modules, and more depending on the options supplied. Within the same directory as the Jython shell script (or batch file) should be another script called jythonc
(or jythonc.bat
). This script is really a wrapper around the file sys.prefix/Tools/jythonc/jythonc.py
that does the work of creating a java file and calling a java compiler to create *.class
files. This means you must have an appropriate Java compiler such as Sun’s javac, available at http://www.javasoft.com/j2se/, or IBM’s jikes, currently available at http://www10.software.ibm.com/developerworks/ opensource/jikes/, to use in conjunction with jythonc
. The Java Runtime Environmenta (JRE) is insufficient in itself because it does not include a compiler. You will need the Java Development Kit (JDK) or Jikes compiler. Note that Microsoft’s jvc compiler is currently not a good choice for compiling Jython code.
Compiled Jython has no performance advantage over interpreted Python. It is often such an immediate concern for those new to Jython that it seems worth mentioning early. The current implementation produces compiled Jython code that performs similarly to using Jython itself to execute a module.
If you run jython A.py
it means that the module A
runs as the top-level, or __main__
, script. If you compile A.py
with jythonc
, you can instead use Java directly to execute the application. Listing 8.1 is a simple Jython module called guess.py
. It will serve as a test subject for compiling a Jython module into runnable Java class files. The examples below assume that the Java compiler, javac, is in your path. If it is not, or if jythonc
has difficulty locating the Java compiler, you can use jythonc
’s −C
switch to specify the compiler. See the jythonc
Options section later in this chapter for more on the −C
switch. Additionally, you may wish to exclude jython.jar
from your classpath
environment variable before running jythonc
because some versions of jythonc
fail when jython.jar
is already in the classpath.
Example 8.1. A Jython Guessing Module for Compiling into Runnable Classes
#file: guess.py import random print "I'm thinking of a number between 1 and 100. Guess what it is." num = random.randint(1, 100) while 1: guess = int(raw_input("Enter guess: ")) if guess==num: print "Correct, the number was %i" %% num break elif abs(guess - num) < 3: print "Close enough. The number was %i" % num break elif guess > num: print "Too high." else: print "Too low"
The shell command irequired to compile Listing 8.1 is this:
> jythonc guess.py
The preceding command assumes jythonc
is in your path. If it is not, you must give the full path to jythonc
—something like this:
c:jython-2.1jythonc guess.py
After running the jythonc
command, you should see output similar to this:
processing guess Required packages: Creating adapters: Creating .java files: guess module Compiling .java to .class... Compiling with args: ['javac', '-classpath', '"C:\jython\jython.jar;.\jpywork;;C:\jython\Tools\jythonc;C:\ch8 examples\.;C:\jython\Lib;C:\jython"', '.\jpywork\guess.java'] 0
You may also receive the following warning depending on the version of the JDK you are iusing:
Note: Some input files use or override a deprecated API. Note: Recompile with -deprecation for details.
Examining the output, we see that jythonc
created a guess.java
file from the guess.py
module, and then called javac to compile the guess.java
file into class files. The full command used to compile guess.java into classes is shown as a list (think .join(args)
to get the actual command). We know jythonc
called javac to do the compiling because javac
is the first item in the args list. It is important to note that the generated Java file may have code specific to the JVM version you are using. This means that using the −target
option may be insufficient as the generated code may contain references to unsupported classes. To compile Jython code for a specific JVM version, you should use that version of the JVM when running jythonc
. This seems to be most often necessary for applets.
If javac is not the Java compiler you use, then you can specify a different compiler in the registry or on the command line. The Jython registry key python.jythonc.compiler
and the −C
command-line switch both specify which Java compiler to use. The appropriate registry line to use jikes instead of javac would be as follows:
python.jythonc.compiler = /path/to/jikes # for *nix users python.jythonc.compiler = c:path ojikes # for win users
Specifying the jikes compiler with the -C
option would appear as follows:
bash> jythonc −C /path/to/jikes guess.py dos> jythonc −C c:path ojikes guess.py
The results of jythonc guess.py
is a directory called jpywork
, which contains the files guess.java
, guess.class
, and guess$_PyInner.class
. The guess.java
file is what jythonc
generates, and the two class files are the results of compiling guess.java
. Compiling Jython code should result in at least two class files: one with the module’s name and an associated $_PyInner
class.
Running jythonc
-compiled classes with a JVM executable requires that the jython.jar
file and both compiled class files (guess.class
and guess$_PyInner.class
) are in the classpath
. If you are within the same directory as the two class files generated from compiling listing 8.1, and you are using the Java executable from Sun’s JDK, this would be the command to run the compiled guess files:
bash> java −cp /path/to/jython.jar:. guess dos> java −cp pathjython.jar;. guess
You should be prompted to guess a number. Sample output from running this program is as follows:
I'm thinking of a number between 1 and 100. Guess what it is. Enter guess: 50 Too high. Enter guess: 25 Close enough. The number was 23
It seems a somewhat common error to forget to include the appropriate directories in the classpath
when first running jythonc
-compiled files. Because jythonc
-compiled files are often used within existing Java frameworks, the compiled class files are often moved into specific directories for that framework. This step seems prone to errors such as not copying all the required files. Remember that jythonc
creates at least two files. Without the inner file (guess$_PyInner.class
in our case) in the classpath
, you should receive the following error message:
Error running main. Can't find: guess$_PyInner
Additionally, jythonc
-compiled classes required the classes found in the jython.jar
file. These can be included in an archive by using special jythonc
options, as we will see later. For now, we must ensure that the jython.jar
file is in the classpath. If the jython.jar
file is not included in the classpath
, you should receive the following error message:
Exception in thread "main" java.lang.NoClassDefFoundError: org/python/core/PyObject
The compiled version of Listing 8.1 still relies on the random module from the sys.prefix/Lib
directory. If the sys.prefix
or sys.path
variables are wrong, you will receive the following error:
Java Traceback: ... Traceback (innermost last): File "C:WINDOWSDesktopch8examplesguess.py", line 0, in main ImportError: no module named random
Jython depends on a Java property called python.home
to locate and read the Jython registry file as well as establish the sys.prefix
, and thus certain sys.path
entries. Jython also creates a cache directory in the python.home
or sys.prefix
directory, which is aptly named cachedir
by default. Information discovered about Java packages is stored in this directory as an essential optimization, but this can be troublesome if python.home
or sys.prefix
have unexpected values. The python.home
property is set as part of jython’s startup script, but compiled Jython doesn’t have this convenience.
This introduces issues concerning Jython’s paths. A Jython module compiled into Java classes requires special steps to ensure it can find Jython modules and packages it depends on and make use of a specific cachedir
. What follows is a list of options you can choose from to do so.
Sun’s Java executables allow the setting of properties with the −D
command-line option. Listing 8.2 is a Jython module designed to expose path problems.
Example 8.2. Jython Module for Clarifying Paths and Properties
# file: paths.py import sys import java print "sys.path=", sys.path print "sys.prefix=", sys.prefix print "python.home=", java.lang.System.getProperty("python.home") # print a random number just so this depends on a # module in the sys.prefix/Lib directory import random print "My lucky number is", random.randint(1,100)
Compile Listing 8.2 with jythonc
paths.py
. Compiling it this way creates class files dubbed runnable in Jython lingo. The word runnable infers that Jython code compiled so that the Java executable can execute it, but it still relies on Jython libraries and follows the standard Jython interpreter behavior. Executing a runnable class should act just like running a Jython script— meaning it would cache Java package information at startup and use the sys.path
to locate modules. This means setting the python.home
property or at least specifying library paths is required.
To continue testing Listing 8.2, change directories in a command shell to where the paths.class
and paths$_PyInner.class
files reside. Then execute paths.class
using the −D
option to set the python.home
property. The command should look similar to that below only using the directories unique to your installation (the command “wraps” because of the long line, but is meant as a single command):
bash> java −Dpython.home="/usr/local/jython-2.1" −cp /usr/local/jython.jar:. paths dos> java −Dpython.home="c:jython-2.1" −cp c:jython-2.1jython.jar;. paths
The results should appear similar to this:
sys.path= ['.', 'c:\jython 2.1\Lib'] sys.prefix= c:\jython 2.1 python.home= c:\jython 2.1 My lucky number is 96
This means Jython’s registry file is found, the sys.prefix
value is accurate, and modules, such as random
in the sys.prefix/Lib
directory, can be found and loaded.
What happens without the −D
option? Without it, the command would look like this:
bash> java −cp /path/to/jython.jar:. paths dos> java −cp c:path ojython.jar;. paths
If the jython.jar
file is still in the Jython installation directory, the results should be similar to that below (replace /
with your platform-specific directory separator):
sys.path= ['.', '/usr/local/jython-2.1/Lib'] sys.prefix= /usr/local/jython 2.1a1/ python.home= None My lucky number is 97
How does Jython know the correct sys.prefix
value without having a proper python.home
value? It’s not part of Jython’s documented behavior, but currently, if jython.jar
is not in the current directory, the sys.prefix
value becomes the directory in which the jython.jar
file was found. If you copy the jython.jar
file into the same directory as the paths.class
file, the results would be different:
dos> java -cp jython.jar;. paths sys.path= ['.', '\Lib'] sys.prefix=. python.home= None Java Traceback: at org.python.core.Py.ImportError(Py.java:180) ... at paths.main(paths.java:72) Traceback (innermost last): File "C:WINDOWSDesktopch8examplespaths.py", line 0, in main ImportError: no module named random
The result in this last case is a python.home
equal to None
. This means that the Jython registry file is not located, and that modules in Jython’s Lib directory (random) cannot be found. This can be resolved by moving the random
module into the same directory as well, because sys.path
will automatically include the current directory, or .
.
It can be easiest at times to explicitly append library paths to the sys.path
variable within the module you intend to compile. This ensures modules within a certain directory are accessible. If you wanted to ensure that Listing 8.2 could find the random module in the /usr/local/jython-2.1/Lib
directory, you can add the following line to paths.py
(after importing sys
of course):
sys.path.append("/usr/local/jython-2.1/Lib") # *nix sys.path.append("c:jython-2.1Lib") # dos
With this being part of the compiled class, modules in that directory will always be found no matter where the python.home
property and sys.prefix
variable point. However, this lacks the flexibility of Java’s classpath
. If an application is moved to another machine, paths become troublesome and functionality across platforms suffers.
Just as you can set the python.home
property with Java’s −D
switch, you can also set the registry’s python.path
or python.prepath
property with the −D
switch. Assume you have compiled Listing 8.2 with jythonc
paths.py
, and you have moved the jython.jar
file into the same directory as the paths.class
and paths$_PyInner.class
files. You can run the application with a command similar to this:
dos>java −Dpython.path="c:\jython-2.1\Lib" −cp jython.jar;. paths
In this case, the sys.path
list appends the python.path
value as part its initialization, and therefore before loading modules. Our example application would locate and load the random
module appropriately. Setting path properties in the command line allows batch files or scripts to dynamically determine this value—an improvement over placing static paths within code.
Freezing an application infers compiling a Jython module in a way that tracks all dependencies during compilation and results in class files for all required modules. A frozen application is self-contained: It does not depend on Jython’s sys.path
directories, it locates modules on Java’s classpath
, and it does not cache package information at startup. Freezing an application requires using jythonc
’s −d
or −−deep
option. To freeze Listing 8.2, use a command like the following:
dos> jythonc −d paths.py dos> jythonc −−deep paths.py
The compilation process will now include the random
module. jythonc
tracks dependencies, such as the random
module, and compiles those modules to Java as well. The results of freezing the paths application (from Listing 8.2) should be four class files:
paths.class
paths$_PyInner.class
random.class
random$_PyInner.class
These class files, plus the classes found in jython.jar
, are all that is required to run the paths application.
If you are at a command prompt, and you are in the same directory as the class files created from compiling Listing 8.2, you can execute the frozen paths program with the following command:
bash> java −cp /usr/local/jython-2.1/jython.jar:. paths dos> java −cp c:jython-2.1jython.jar;. paths
Note that the classes within jython.jar
are still required and must appear in the classpath
.
It does not work to compile paths.py
then random.py
separately. jythonc
must know if dependencies are to be Java classes or Jython modules at com-pile-time. This class/module dichotomy introduces another important point. The class files created from compiling a module with jythonc
cannot be used within Jython as if they are modules. jythonc
has made them Java classes and to revert them back into modules is tricky.
Writing your own import tools allows greater control but also has some limitations. With a custom import, you can control the location from which modules load. This allows you to use zip
files, jar
files, classpath
resources, remote URL resources, and more as the source for modules. The limitation, however, is the current difficulty in dealing with pre-compiled modules. The duality of Python modules (* .py
files) and Py
classes (modules that are compiled into $py.class
files) makes loading pre-compiled modules troublesome in Jython. The ability to load a compiled Jython module ($py.class
) without having to freeze all module dependencies is a desired feature dubbed poor man freezing, but it is not yet implemented.
Listing 8.3 is a simple implementation of custom module loading that loads modules (*. py
files) from the classpath
. This allows access to *. py
files that are packaged within jar
files—something often desired for delivering applications. In Listing 8.3, loadmodule
is the custom import function, and the myTest.py
module is the simple companion module loaded with loadmodule
.
The important aspects of the loadmodule
function are as follows:
The classloader
Reading the module
Creating a module instance with the new
module
The exec
function
When examining the contents of the sys
module, you will see three functions related to class loaders: classLoader
, getClassLoader
, and setClassLoader
. Currently sys.classLoader
is only set in a jythonc
-compiled class. Listing 8.3 includes two extra lines for working with uncompiled code (they are commented out in the listing).
After acquiring a class loader, the loadmodule
function begins loading the desired module. The steps to reading the module are as follows:
Create a byte
[]
buffer with the jarray
module to use as an argument to the stream’s read()
method.
Read from the buffer.
Append data to the module string until the read()
method reports the end of file (-1).
The new module enables the dynamic creation of classes, functions, methods, instances, and modules; the loadmodule function makes use of its dynamic module creation to create a module object. This module should be registered with the list of all modules loaded, which is held in the PyStringMap
sys.modules
, but also needs a reference in the current namespace, so both sys.modules[name]
and mod
are assigned the new module instance.
The remaining work in setting up the module is executing it, but it must be executed in the namespace of the new module instance created— mod
(otherwise it executes in the top-level environment, or __main__
). in mod.__dict__
is added after the exec
statement to force bindings resulting from the execution to appear in the mod.__dict__
mapping object.
Example 8.3. Custom Module Import
# file: customimp.py import sys import new import jarray import java def loadmodule(name, bufferSize=1024): filename = name + ".py" cl = sys.getClassLoader() if cl == None: cl = java.lang.ClassLoader.getSystemClassLoader() r = cl.getResourceAsStream(filename) if not r: raise ImportError, "No module named %s" % name moduleString = "" buf = jarray.zeros(bufferSize, "b") while 1: readsize = r.read(buf) if readsize==-1: break moduleString += str(java.lang.String(buf[0:readsize])) sys.modules[name] == mod = new.module(name) exec(moduleString) in mod.__dict__ return mod myTest = loadmodule("myTest") print myTest.test()
The simple myTest
module used to test the custom loading is as follows:
# file: myTest.py def test(): return "It worked"
Listing 8.3 is a nice compromise because even if you freeze the application, you can still have flexibility with the *.py
files that you load with loadmodule
. Any required *.py
files can be updated, added, or removed from a resource without having to recompile the anything.
To freeze Listing 8.3 and include required Jython classes all in a single jar
file, use the following jythonc command:
jythonc −all −−jar myapp.jar customimp.py
You can now add the myTest.py
file to myapp.jar
with the following:
jar −uf myapp.jar myTest.py
The myapp.jar
file contains all you need to run the application, but you can easily update the myTest.py
file without having to recompile anything. The following command runs the customimp
module:
>java −cp myapp.jar customimp It worked
jythonc
has a number of options that allow special treatment of the compiled files, dependencies, compiler options, and more. jythonc
’s syntax requires that options are specified first, followed by the modules to be compiled:
jythonc options modules
The modules can be full paths to python modules, such as the following:
jythonc /usr/local/jython2.1/Lib/ftplib.py
Or, modules in the sys.path
may be listed by just the module’s name as it would be used in an import
statement:
jythonc ftplib
Table 8.1 shows each of jythonc
’s options and what it does. Notice that each option has a long and short form that may be used interchangeably. Some of the jythonc
options are also available as registry keys. Equivalent registry keys are noted where applicable.
Table 8.1. jythonc Options
Option |
Description |
---|---|
−−
|
The −− |
−−
|
The −− |
−−
|
The −− |
−−
|
The −− |
−−
|
The −− |
−−
|
|
−−
|
The −− |
−−
|
The −− |
−−
|
The −− |
−−
|
The −− |
|
The |
|
The |
|
The |
Table 8.2 shows sample usages of jythonc
coupled with explanations of how the options specified affect the compilation.
Table 8.2. Usages of jythonc
Command |
Result |
---|---|
|
This compiles |
|
This compiles |
|
This compiles all modules in the current directory into Java class files. All class files will be part of the java package |
|
This compiles the |
|
This compiles the |
A compiled Jython class can behave like a native Java class if it is Java-compatible. To make a Jython class Java-compatible, you must consider the following three guidelines:
The Jython class must subclass a Java class or interface.
The Jython class may include Java method signature hints called sig-strings.
The Jython class must be within a module of the same name. The Java-compatible class A
should be within the file A.py
.
The first guideline is fairly simple: Subclass a Java class or interface. If you do not need the functionality from a specific Java class, subclass java.lang.Object
. You may still inherit from one or more Jython classes— jythonc
does not alter Jython’s inheritance rules, it’s just an additional requirement for those Jython classes used within Java. This doesn’t apply to runnable or frozen applications, it is only when you require a compiled Jython class to masquerade as a Java class.
The second guideline is the use of @sig
strings. A @sig
string is really a doc
string with a specific format. The format is the literal @sig
followed by a valid Java method signature. Suppose you have the following Jython method:
def greet(self, name): return "Hello " + name
The same method with an added @sig
string would appear as follows:
def greet(self, name): "@sig public String greet(String name)" return "Hello " + name
The @sig
in this case specifies a public method with a String
object as a return value and a String
object as the parameter name
. From Java, this method now acts as if it were Java code with the signature specified in the @sig
string.
Not all methods require @sig
strings. If you are overriding a method that appears in the Java superclass (see requirement #1), you need not specify the method signature. jythonc
is smart enough to get this information from the Java superclass. In addition, if you never intend to call a certain method from Java, you need not specify a @sig
string. In other words, if method A
calls method B
, and method A
has a @sig
string, then method A
is callable from Java, while method B
is not; however, method B
is still callable from method A
. Intra-class methods that are not accessible from Java are still accessible from Jython. This acts in a sense as enforced abstraction.
The third requirement is that the name of the class and the module that contains it must match. Listing 8.4 is a Jython module called message.py
. Listing 8.4 also contains a class called message
. Note the matching class and module name meet the third requirement for Java-compatible classes.
The message
class in Listing 8.4 has two methods: __init__
and showMessage
, of which, the showMessage
method has a @sig
string. The __init__
method does not, but it is still accessible from Java. Why? jythonc
is able to determine this information from the Java superclass. The catch is that of the three constructors in the superclass, all three now point to the single __init__
method in the subclass. The subclass chooses to implement only the one signature, and fortunately, the number of parameters makes it clear which one. Remember that only methods that do not override base class methods require @sig
strings. You may choose to supply a @sig
string even where it is not required—there is no harm in that, but there is no advantage.
Example 8.4. Java-Compatible Jython Class
# file: message.py import java class message(java.awt.Frame): def __init__(self, name): self.setTitle(name) self.label = java.awt.Label("", java.awt.Label.CENTER) action = lambda x: java.lang.System.exit(0) button = java.awt.Button('OK', actionPerformed=action) p = java.awt.Panel(java.awt.GridLayout(2,1,2,2)) p.add(self.label) p.add(button) self.add(p) def showMessage(self, text): "@sig public void showMessage(String text)" self.label.setText(text) self.pack() self.show() if __name__ == '__main__': m = message("Test Message") m.showMessage("I look like Java, but I'm not")
To use the message class in Listing 8.4 from within Java, it must be compiled with jythonc
. No special options are required, so this is as simple as jythonc
message.py
(assuming you are in the same directory as message.py
and jythonc
is in your path). Again note that depending on your compiler version, you may receive a warning message concerning the use of a deprecated API. The output from running jythonc
message.py
should look similar to this:
processing message Required packages: java.lang java.awt Creating adapters: java.awt.event.ActionListener used in message Creating .java files: message module message extends java.awt.Frame Compiling .java to .class... Compiling with args: ['C:\JDK1.3.1\bin\javac', '-classpath', '"C:\jython 2.1a1\jython.jar;;.\jpywork;;C:\jython 2.1a1\Tools\jythonc;C:\WINDOWS\Desktop\ch8examples\.;C:\jython 2.1a1\Lib;C:\jython 2.1a1;c:\jython 2.1a1\Lib\site-python"', '.\jpywork\message.java'] 0
The messages you receive from jythonc
are valuable. What is particularly valuable when compiling Java-compatible classes such as message.py
is the information printed when jythonc
is creating the *.java
files:
Creating .java files: message module message extends java.awt.Frame
It is especially important to watch for the appropriate extends message, which ensures that you have met the first of the Java-compatible requirements.
Now that you have message.class
and message$_PyInner.class
, how do you use them from Java? The only requirement is placing both generated class files and the jython.jar
file in the classpath
. Otherwise, it acts no different than any other Java class. Listing 8.5 is a simple Java class that uses the message class as if it were native Java.
Example 8.5. Using a Compiled Jython Class in Java
//file MessageTest.java import message; public class MessageTest { public static void main(String[] args)) { message m = new message("Test"); m.showMessage("I look like Java, but I'm not"); } }
To compile Listing 8.5, include jython.jar
and the message
classes in the classpath
. From within the directory containing message.class
and message$_PyInner.class
, and using javac from the Sun jdk, the command is as follows:
javac −classpath .;/path/to/jython.jar MessageTest.java
Running the MessageTest
class also requires that the jython.jar
and both message class files be in the classpath. If the classes message.class
, message$_PyInner.class,
and MessageTest.class
are in the same directory, run MessageTest
with the following command:
java −cp .;/path/to/jython.jar MessageTest
The small message box similar to that in Figure 8.1 should appear.
Providing a specific method signature in a @sig
string does not automatically allow for overloaded methods. Yes, a @sig
string specifies a unique signature, but as noted in Chapter 6, “Classes, Instances, and Inheritance,” if Jython overrides a Java method, it must handle all overloaded methods of that name. You cannot add a @sig
string in hopes of circumventing this generalization. Like-named methods in a Java superclass cannot automatically handle parameter types that differ from the @sig
string. Nor can you define multiple, like-named methods in a Jython subclass to handle differing parameter types based solely on @sig
strings.
Listing 8.6 demonstrates with three classes called Base
, sub
, and App
.
Example 8.6. Overloaded Methods and jythonc
// file: Base.java public class Base { public String getSomething(String s) { return "string parameter. Base class."; } public String getSomething(int i) { return "int parameter. Base class."; } public String getSomething(float f) { return "float parameter. Base class."; } } #- - - - - - - #file: Sub.py import Base class Sub(Base): def getSomething(self, var): "@sig public String getSomething(String var)" return str(type(var)) + " Jython subclass.." //- - - - - - - - //file: App.java import Sub; public class App { public static void main(String args[]) { Sub s = new Sub(); System.out.println(s.getSomething("A string")); System.out.println(s.getSomething(1)); System.out.println(s.getSomething((float)1.1)); } }
The base Java class called Base
has an overloaded method called getSomething
. Three versions of this method collectively allow for String
, int
, and float
parameter types. The Jython subclass, called Sub
, overrides the getSomething
method, but has a @sig
string specifying only the String
parameter type. This does not restrict it to overriding that specific method signature, however. Method signature information is also gleaned from the superclass, so all methods in the superclass with the same name, but different signatures, are automatically directed to the single method of that name in the Jython subclass.
To compile the files in Listing 8.6, place them all in the same directory, change the console’s working directory to that where they reside, then compile with the following commands (in the specified order):
javac Base.java jythonc −w . Sub.py javac −classpath . App.java
Execute App.java
with the following command from within the same directory:
dos>java −cp .;path ojython.jar App *nix>java −c; .:/path/to/jython.jar App
The results should appear similar to this:
org.python.core.PyString Jython subclass. org.python.core.PyInteger Jython subclass. org.python.core.PyFloat Jython subclass.
You can see that jythonc
has automatically intercepted each of the Base
class’s overloaded methods with the name getSomething
. The single Jython method by this name is expected to handle each of these conditions, and trying to circumvent this generalization with a @sig
string gained nothing.
The rigidity of adding Java method signatures can feel contradictory to Jython’s dynamic nature. Many common patterns that occur in Jython depend on dynamic special methods like __getattr__
and __setattr__
, or other mechanisms that don’t easily translate into compile-time checks. Additionally, module-global objects might be unclear when using jythonc
to create Java-compatible classes. The focus on the class shadows the value of module-global identifiers: those objects defined in a module, but not within the java-compatible class. Although a Jython class and those methods with @sig
strings are callable from Java, module-global objects are not. However, the Jython class may use module-level code, and module-level code may even alter the details of a class used by Java (as in Listing 8.8). Listing 8.8 uses a bit of Jython’s dynamicism in a compiled class as well as lends clarity to module-global objects.
To set the stage for Listing 8.8, imagine prototyping a Java application with compiled Jython classes. What do you do about static methods? Jythonc
doesn’t allow you to specify static in a @sig
string, so Listing 8.8 was invented to create the illusion of static methods in a compiled Jython class. It doesn’t create a legitimate static method—the word “static” doesn’t appear in it’s signature, but it does emulate one. It does so with the use of other module-global code that is not callable from Java. It’s useful to remember that Java may call only specific methods in the compiled Jython class, but Jython code has no restrictions. The generated method signatures are a hook into Jython and need not alter its flexibility.
Listing 8.8 defines a class called JavaPrototype
, which contains one function definition: test
. The test
function is empty except for the @sig
string. All this test
function does is make jythonc
generate a desired Java method signature; it does not have anything to do with the run-time functionality. Why? Because the last line of the module assigns a callable instance of another class to test
. The staticfunctor
class in Listing 8.8 defines __call__
, and an instance of this class is what provides the desired method functionality. If you were wondering why the arg
identifier appears in the test method’s @sig
string, it is because we are actually trying to generate a signature for the __call__
method in the second class. Following is a summary of what happens in Listing 8.8:
Create a phony method and @sig
string to trick jythonc
into adding the desired Java method signature.
Define a class that implements __call__
. The __call__
definition is the actual functionality desired so the method signature (step 1) should fit its needs.
Assign the identifier from the phony method to an instance of a class containing the desired __call__
method.
Example 8.7. Creating a Static-Like Method in Compiled Jython
# file: JavaPrototype.py import java.lang.Object class JavaPrototype(java.lang.Object): def test(self): # This will become static-like "@sig public void test(String arg)" class staticfunctor(java.lang.Object): def __call__(self, arg): print "printing %s from static method" %% arg JavaPrototype.__dict__['test'] = staticfunctor()
How can it be static if the @sig
string doesn’t specify static? This is a stretch, but it acts static in the Jython sense. If you use the JavaPrototype
module within Jython, you can test to see that multiple instances of the class share a single, common instance of the test function:
>>> import JavaPrototype >>> p1 = JavaPrototype.JavaPrototype() >>> p2 = JavaPrototype.JavaPrototype() >>> p1.test == p2.test 1 >>> p1.test is p2.test 1 >>> print id(p1.test), id(p2.test) 5482965 5482965
The interactive example above uses the JavaPrototype
module, not the compiled classes. This invites a warning about compiled Jython classes. A compiled Jython class is designed to work in Java, not in Jython. You should use modules in Jython and reserve jythonc
-compiled classes for use within Java.
Listing 8.8 is a small Java test class that imports and uses the compiled JavaPrototype
class to confirm that it behaves properly as a Java class as well. This assumes that you have used jythonc
to compile the JavaPrototype
module into class files with jythonc
JavaPrototype
.
Example 8.8. Using the JavaPrototype Class from Within Java
// file: Test.java import JavaPrototype; public class Test { public static void main(String[] args)) { JavaPrototype i1 = new JavaPrototype(); JavaPrototype i2 = new JavaPrototype(); i1.test("THIS"); i2.test("THAT"); } } Place in the same direcotry as JavaPrototype.class and JavaPrototype$_PyInner.class, then compile with: javac −classpath . test.java
Output from running "java "cp .;path ojython.jar test" # dos "java −cp .:/path/to/jython.jar test" # *nix: printing THIS from static method printing THAT from static method