Differences between revisions 1 and 17 (spanning 16 versions)
Revision 1 as of 2007-12-24 06:16:31
Size: 10325
Comment:
Revision 17 as of 2009-08-03 06:31:10
Size: 12322
Editor: 2002:47cc:b55b:0:221:e9ff:fee5:eac1
Comment:
Deletions are marked like this. Additions are marked like this.
Line 12: Line 12:
Starting in Jython 2.5, Python type are exposed from Java code by adding
several types of annotations to the Java fields and methods to define the
Starting in Jython 2.5, Python types are exposed from Java code by adding
several different annotations to fields and methods on the Java class to define the
Line 15: Line 15:
bytecode postprocessor then operates on the compiled classes to generate the
final bytecode that contains code
to build the type's dict and descriptors. An
Ant task, `org.python.expose.generate.ExposeTask`, handles finding the classes
to expose, running the postprocessor on them, and writing them back out. This
bytecode processor then adds code to the compiled class to build the type's dict and descriptors. An
Ant task, ``org.python.expose.generate.ExposeTask``, handles finding the classes
to expose, running the processor on them, and writing them back out. This
Line 26: Line 25:
`org.python.expose.ExposedType` annotation on the actual class to indicate that
it should be exposed at all. This annotation goes on the class itself and has
two optional fields. The first, name, defines what the type will be called in
Python. If it isn't specified, the type's name is just assumed to be the same
as the Java class name. The second field, base, indicates the base type this
type extends. It must be another Java class that's likewise been annotated
with `@ExposedType` and just sets up the type hierarchy in Python. If
unspecified, it's assumed that this type extends from the base newstyle Python
type, object. Unlike classes defined in Python code, types defined in Java can
only use single inheritance.
``org.python.expose.ExposedType`` annotation on the actual class to indicate
that it should be exposed. This annotation goes on the class itself and has
three optional fields. The first, ``name``, defines what the type will be
called in Python. If it isn't specified, the type's name is just assumed to be
the same as the Java class name. The second field, ``base``, indicates the
base type this type extends. It must be another Java class that's likewise
been annotated with ``@ExposedType`` and sets up the type hierarchy in Python.
If unspecified, it's assumed that this type extends from the base newstyle
Python type, ``object``. Unlike classes defined in Python code, types defined
in Java can only use single inheritance. The third, ``isBaseType``, defines if
this type can be subclassed from Python code. When unspecified this value
defaults to true. Any type defined in CPython that lacks a Py_TPFLAGS_BASETYPE
bit in its tp_flags should have isBaseType set to false in Jython.
Line 37: Line 39:
Once the class has `@ExposedType` on it, it can be fed into the type bytecode Once the class has ``@ExposedType`` on it, it can be fed into the type bytecode
Line 41: Line 43:
the newly exposed class needs to be added to `CoreExposed.includes` file in the the newly exposed class needs to be added to ``CoreExposed.includes`` file in the
Line 44: Line 46:
`org/python/core/PyObject.class` for PyObject. After adding the class to ``org/python/core/PyObject.class`` for PyObject. After adding the class to
Line 51: Line 53:
`@ExposedMethod`. `@ExposedMethod` can be applied to non-static public,
protected or package protected method on the class. When the bytecode
processor encounters an `@ExposedMethod`, it generates a new inner class
extending PyMethodDescriptor that simply calls this method when bound. Because
``org.python.expose.ExposedMethod``. ``@ExposedMethod`` can be applied to non-static methods on the class that are public,
protected or package protected. When the bytecode
processor encounters an ``@ExposedMethod``, it generates a new inner class
extending PyMethodDescriptor that simply calls the annotated method when bound. Because
Line 57: Line 59:
its generally a good idea to make exposed methods final. This ensures that it's generally **a good idea to make exposed methods final**. This ensures that
Line 59: Line 61:
calls the same code when accessed directly off of the type. calls the same code when accessed directly off of the type. Also, exposed methods
should avoid to call other virtual methods, directly or indirectly, unless the
interactions with subclasses overriding the invoked methods are clearly documented
and understood.
Line 61: Line 66:
The generated descriptor maps the generic PyObject `__call__` method into the
actual exposed method. Like `__call__`, it will pass up to four arguments
The generated descriptor maps the generic PyObject ``__call__`` method into the
actual exposed method. Like ``__call__``, it will pass up to four arguments
Line 64: Line 69:
must take the generic `__call__` arguments, `PyObject[], String[]`. For must take the generic ``__call__`` arguments, ``PyObject[], String[]``. For
Line 66: Line 71:
PyObject argument into a String, boolean or int if the method takes that type. PyObject arguments into Strings or Java primitives if the method takes that type.
Line 68: Line 73:
If the method arguments aren't String, boolean or int, they must be a regular
PyObject and the method will do its own type checking.
If the argument isn't a String or primitive, it must be a regular
PyObject and the method must do its own type checking.
Line 71: Line 76:
The return type from the method can be coerced in a similar fashion. If the a
returns of String will be wrapped in a PyString, int in PyInteger and boolean
The return type from the method can be coerced in a similar fashion. Returns of
String and char will be wrapped in a PyString; int, byte and short in PyInteger; long in PyLong; float and double in PyFloat; and boolean
Line 76: Line 81:
As with `@ExposeType`, there are several fields on `@ExposedMethod` that
control how it appears in Python. The `names` field operates similarly to the
`name` field on @ExposedType, except that it accepts multiple values in case
As with ``@ExposeType``, there are several fields on ``@ExposedMethod`` that
control how it appears in Python. The ``names`` field operates similarly to the
``name`` field on ``@ExposedType``, except that it accepts multiple values in case
Line 80: Line 85:
method `int_toString` on `PyInteger` is exposed with `names = {"__repr__",
"__str__"}` as it works as both `__repr__` and `__str__` for int. If `names`
method ``int_toString`` on ``PyInteger`` is exposed with ``names = {"__repr__",
"__str__"}`` as it works as both ``__repr__`` and ``__str__`` for int. If ``names``
Line 87: Line 92:
them. For example, `PyInteger` has an exposed method `int___add__` which
appears in its type dict as `__add__`. If the method name doesn't start with
them. For example, ``PyInteger``'s exposed method ``int___add__`` doesn't set names, so it
appears in its type dict as ``__add__``. If the method name doesn't start with
Line 91: Line 96:
The next field, `defaults`, allows the method to specify that some of its later The next field, ``defaults``, allows the method to specify that some of its
Line 98: Line 103:
If three argumetns are given, the defaults are ignored. If only a single If three arguments are given, the defaults are ignored. If only a single
Line 101: Line 106:
arguments. The defaults can take four types of values: arguments. The defaults can take three types of values:
Line 104: Line 109:
  the String `null` produces a Java null   the String ``null`` produces a Java null
Line 106: Line 111:
  the String `Py.None` produces the Python None value
integer
  if the method takes an int in the default's position, the string will be parsed to yield an int value
boolean
  if the method takes a boolean in the default's position, `true` or `false` can be specified
  the String ``Py.None`` produces the Python None value
primitive
  if the method takes a Java primitive(boolean, byte, short, char, int, long, float or double) in the default's position, the string will be parsed using the primtive type's Java wrapper(ie java.lang.Boolean for boolean)
Line 112: Line 115:
Also like argument coercion, they can only be used on methods taking up to four
arguments.

The final field on `@ExposedMethod` is `type`. This field takes one of three
MethodType enums. The aptly named default value, `DEFAULT`, indicates that the
The final field on ``@ExposedMethod`` is ``type``. This field takes one of three
MethodType enums. The aptly named default value, ``DEFAULT``, indicates that the
Line 118: Line 118:
normal coercion directly. `BINARY` indicates that the method is a binary
operation like `__add__` or `__sub__`. For these types, the descriptor checks
if the method returned null, and if so, it raises a NotImplemented exception.
The `CMP` type is only for `__cmp__` methods. If used, it checks if the method
returns `-2`, and if so, raises a TypeException.
normal coercion directly. ``BINARY`` indicates that the method is a binary
operation like ``__add__`` or ``__sub__``. For these types, the descriptor checks
if the method returned null, and if so, it raises a NotImplemented exception. This is generally used in the case where the binary method doesn't handle the passed in type.
The ``CMP`` type is only for ``__cmp__`` methods. If used, it checks if the method
returns ``-2``, and if so, raises a TypeException.  This is the same sort of type check that ``BINARY`` is doing with null.
Line 127: Line 127:
A trio of annotations are used to expose a field on a type. Each handles a
different aspect of accessing the field.
`@ExposedGet` takes care of read
access. It can be applied to a method that takes no arguments and returns a
non-primiti
ve value or to a non-primitive field. If on a method, that method
will called ev
ery time read access is made on that field on instances of the
A trio of annotations are used to expose a field on a type, each handling a
different aspect of accessing the field
. ``@ExposedGet`` takes care of read
access. It can be applied to a method that takes no arguments and returns a value, or to a field. If on a method, that method will be called every time read access is made on that field on instances of the
Line 133: Line 131:
the instance and return it. `@ExposedSet` can also be applied to a field or a the instance and return it. ``@ExposedSet`` can also be applied to a field or a
Line 135: Line 133:
same type as the `@ExposedGet` with the same name. `@ExposedDelete` is only same type as the ``@ExposedGet`` with the same name. ``@ExposedDelete`` is only
Line 137: Line 135:
`del typeinstance.fieldname` is invoked in Python, that delete method will be
called. The method must return void. Neither `@ExposedDelete` or
`@ExposedSet` can be used if an `@ExposedGet` of the same doesn't exist on the
type. The names of the exposed field can be specified as `name` in the
``del typeinstance.fieldname`` is invoked in Python, that delete method will be
called. Neither ``@ExposedDelete`` or
``@ExposedSet`` can be used if an ``@ExposedGet`` of the same doesn't exist on the
type. The names of the exposed field can be specified as ``name`` in the
Line 142: Line 140:
of the field or the method. of the field or method.
Line 149: Line 147:
type instantiable by adding a `__new__` method to it. This is done with the
`@ExposedNew` annotation. If there is no `@ExposedNew` in the class, the type
type instantiable by adding a ``__new__`` method to it. This is done with the
``@ExposedNew`` annotation. If there is no ``@ExposedNew`` in the class, the type
Line 152: Line 150:
calling its constructors directly. See `org.python.core.PyNone` for an example calling its constructors directly. See ``org.python.core.PyNone`` for an example
Line 154: Line 152:
needs an `@ExposedNew`. There are two ways it can be used, a simple way for
types that allow their subtypes to completely override the `__new__` process,
and a more complicated version for types that need to have their `__new__`
needs an ``@ExposedNew``. There are two ways it can be used, a simple way for
types that allow their subtypes to completely override the ``__new__`` process,
and a more complicated version for types that need to have their ``__new__``
Line 159: Line 157:
In the simple form, `@ExposedNew` is applied to an instance method that takes
the standard Jython call arguments, `PyObject[] args, String[] keywords`. In
In the simple form, ``@ExposedNew`` is applied to an instance method that takes
the standard Jython call arguments, ``PyObject[] args, String[] keywords``. In
Line 162: Line 160:
`org.python.core.PyOverridableNew` and the method annotated with `@ExposedNew`
is called as __init__ as part of that process. This allows subtypes to
``org.python.core.PyOverridableNew`` and the method annotated with ``@ExposedNew``
is called as __init__ as part of that process for instantiating the defined type directly. This allows subtypes to
Line 166: Line 164:
In the more complex form, `@ExposedNew` must be applied to a static method that
takes the arguments `PyNewWrapper new_, boolean init, PyType subtype,
PyObject[] args, String[] keywords`. In this case, the method has full
In the more complex form, ``@ExposedNew`` must be applied to a static method that
takes the arguments ``PyNewWrapper new_, boolean init, PyType subtype,
PyObject[] args, String[] keywords``. In this case, the method has full
Line 171: Line 169:
that must be called for every instance of that object. See PyInteger's int_new
for an example of this type of `@ExposedNew`.
that must be called for every instance of that object. See PyInteger's ``int_new``
for an example of this type of ``@ExposedNew``.
Line 174: Line 172:
With either form, there can be only one `@ExposedNew` per class. With either form, there can be only one ``@ExposedNew`` per class.

Post-processing
===============

Exposing Python types involves creating classes that the class loader can find, and then (as above) calling PyType.fromClass() on some Class. The classes get created by the ExposedTypeProcessor class in org.python.expose.generate, but as of this writing (r5593), these classes are not included in the Jython jar. It is intended that eventually ExposedTypeProcessor and related classes will be shipped separately, as a support jar. Your build.xml should also have a clause that runs the ExposeTask on the relevant classes, like this:

    <taskdef name="expose" classname="org.python.expose.generate.ExposeTask">
      <classpath>
        <path refid="classpath" />
        <pathelement path="${compile.dir}" />
      </classpath>
    </taskdef>

    <expose srcdir="${compile.dir}"
            destdir="${exposed.dir}"
            includesfile="${base.dir}/JythonExposed.includes"/>

(Taken from jython's build.xml.) You probably want to make your non-preprocessed classes (all the ones in JythonExposed.includes) get compiled someplace else, and only have the compiled ones show up in this "exposed" directory, so that you don't have two versions of each class on your classpath, and be careful if you're using an IDE.

How to Expose Python Types from Java

Introduction

Starting in Jython 2.5, Python types are exposed from Java code by adding several different annotations to fields and methods on the Java class to define the fields and methods that will be visible on the resulting Python type. A bytecode processor then adds code to the compiled class to build the type's dict and descriptors. An Ant task, org.python.expose.generate.ExposeTask, handles finding the classes to expose, running the processor on them, and writing them back out. This document describes how to use this type exposing system.

Adding @ExposedType and generating bytecode

The first step in exposing a class is to use the org.python.expose.ExposedType annotation on the actual class to indicate that it should be exposed. This annotation goes on the class itself and has three optional fields. The first, name, defines what the type will be called in Python. If it isn't specified, the type's name is just assumed to be the same as the Java class name. The second field, base, indicates the base type this type extends. It must be another Java class that's likewise been annotated with @ExposedType and sets up the type hierarchy in Python. If unspecified, it's assumed that this type extends from the base newstyle Python type, object. Unlike classes defined in Python code, types defined in Java can only use single inheritance. The third, isBaseType, defines if this type can be subclassed from Python code. When unspecified this value defaults to true. Any type defined in CPython that lacks a Py_TPFLAGS_BASETYPE bit in its tp_flags should have isBaseType set to false in Jython.

Once the class has @ExposedType on it, it can be fed into the type bytecode processor to fill in the additional bytecode necessary to actually make the class work as a type. The resulting type won't have any fields or methods defined on it, but they can be added later. To make the Ant task aware of it, the newly exposed class needs to be added to CoreExposed.includes file in the base of the jython checkout. CoreExposed just contains a single line for each file to be exposed with the full class name as a path in it, ie org/python/core/PyObject.class for PyObject. After adding the class to CoreExposed, executing ant will compile the code and process the new bytecode.

Exposing methods with @ExposedMethod

To actually add methods to the type, they need to be exposed with org.python.expose.ExposedMethod. @ExposedMethod can be applied to non-static methods on the class that are public, protected or package protected. When the bytecode processor encounters an @ExposedMethod, it generates a new inner class extending PyMethodDescriptor that simply calls the annotated method when bound. Because it's being called from a method descriptor, and Python method descriptors are available on types regardless of their location in the inheritance hierarchy, it's generally a good idea to make exposed methods final. This ensures that subclasses won't override the behavior of the method, so the descriptor always calls the same code when accessed directly off of the type. Also, exposed methods should avoid to call other virtual methods, directly or indirectly, unless the interactions with subclasses overriding the invoked methods are clearly documented and understood.

The generated descriptor maps the generic PyObject __call__ method into the actual exposed method. Like __call__, it will pass up to four arguments directly to the method. If more than four arguments are required, the method must take the generic __call__ arguments, PyObject[], String[]. For methods with four or less arguments, the descriptor will also coerce its PyObject arguments into Strings or Java primitives if the method takes that type. If the PyObject can't be coerced to that type, a TypeException will be raised. If the argument isn't a String or primitive, it must be a regular PyObject and the method must do its own type checking.

The return type from the method can be coerced in a similar fashion. Returns of String and char will be wrapped in a PyString; int, byte and short in PyInteger; long in PyLong; float and double in PyFloat; and boolean in PyBoolean. If the method returns void, PyNone will be returned in its place. If it doesn't return any of these types, it must return PyObject.

As with @ExposeType, there are several fields on @ExposedMethod that control how it appears in Python. The names field operates similarly to the name field on @ExposedType, except that it accepts multiple values in case the method needs to appear under multiple names in the type. For instance, the method int_toString on PyInteger is exposed with names = {"__repr__", "__str__"} as it works as both __repr__ and __str__ for int. If names isn't specified, the Java method's name is used to determine what the method will be exposed as. If the name starts with the type name followed by an underscore, the method will be exposed as the portion of its name following the underscore. This allows final, package protected methods to be specified for types that on PyObject as well without requiring setting the name on all of them. For example, PyInteger's exposed method int___add__ doesn't set names, so it appears in its type dict as __add__. If the method name doesn't start with the type name and an underscore, it's just exposed as the full method name.

The next field, defaults, allows the method to specify that some of its arguments are optional, and if they aren't supplied, what values fill in. If the Java method takes three arguments, and two defaults are given in the annotation, it can be called from Python with anywhere from one to three arguments. If only one argument is given, the first default is used for the second argument, and the second default for the third argument. If two arguments are given, the second default is again used for the third argument. If three arguments are given, the defaults are ignored. If only a single default is given, it will only be used for the final argument. As with argument coercion, defaults can only be used with methods taking up to four arguments. The defaults can take three types of values:

null
the String null produces a Java null
Py.None
the String Py.None produces the Python None value
primitive
if the method takes a Java primitive(boolean, byte, short, char, int, long, float or double) in the default's position, the string will be parsed using the primtive type's Java wrapper(ie java.lang.Boolean for boolean)

The final field on @ExposedMethod is type. This field takes one of three MethodType enums. The aptly named default value, DEFAULT, indicates that the method descriptor should simply call the exposed method and return using the normal coercion directly. BINARY indicates that the method is a binary operation like __add__ or __sub__. For these types, the descriptor checks if the method returned null, and if so, it raises a NotImplemented exception. This is generally used in the case where the binary method doesn't handle the passed in type. The CMP type is only for __cmp__ methods. If used, it checks if the method returns -2, and if so, raises a TypeException. This is the same sort of type check that BINARY is doing with null.

Exposing fields with @ExposedGet, @ExposedSet and @ExposedDelete

A trio of annotations are used to expose a field on a type, each handling a different aspect of accessing the field. @ExposedGet takes care of read access. It can be applied to a method that takes no arguments and returns a value, or to a field. If on a method, that method will be called every time read access is made on that field on instances of the type. If on a field, the descriptor will just directly access that field on the instance and return it. @ExposedSet can also be applied to a field or a method. If used on a method, the method must take a single argument of the same type as the @ExposedGet with the same name. @ExposedDelete is only allowed on methods that return void and take no arguments. If specified, when del typeinstance.fieldname is invoked in Python, that delete method will be called. Neither @ExposedDelete or @ExposedSet can be used if an @ExposedGet of the same doesn't exist on the type. The names of the exposed field can be specified as name in the annotation, or if that isn't specified the name is taken directly from the name of the field or method.

Making the type instantiable with @ExposedNew

The final step in making a Java class usable as a Python type is to make the type instantiable by adding a __new__ method to it. This is done with the @ExposedNew annotation. If there is no @ExposedNew in the class, the type won't be instantiable from Python. It can still be created from Java by calling its constructors directly. See org.python.core.PyNone for an example of this. However in most cases, a type should be creatable from Python so it needs an @ExposedNew. There are two ways it can be used, a simple way for types that allow their subtypes to completely override the __new__ process, and a more complicated version for types that need to have their __new__ invoked by every subclass.

In the simple form, @ExposedNew is applied to an instance method that takes the standard Jython call arguments, PyObject[] args, String[] keywords. In this form, the basic new functionality is handled by org.python.core.PyOverridableNew and the method annotated with @ExposedNew is called as __init__ as part of that process for instantiating the defined type directly. This allows subtypes to completely redefine new and create objects however they like.

In the more complex form, @ExposedNew must be applied to a static method that takes the arguments PyNewWrapper new_, boolean init, PyType subtype, PyObject[] args, String[] keywords. In this case, the method has full responsibility for creating and initting the object and will be invoked for every subtype of this exposed type. Essentially it's for object instantation that must be called for every instance of that object. See PyInteger's int_new for an example of this type of @ExposedNew.

With either form, there can be only one @ExposedNew per class.

Post-processing

Exposing Python types involves creating classes that the class loader can find, and then (as above) calling PyType.fromClass() on some Class. The classes get created by the ExposedTypeProcessor class in org.python.expose.generate, but as of this writing (r5593), these classes are not included in the Jython jar. It is intended that eventually ExposedTypeProcessor and related classes will be shipped separately, as a support jar. Your build.xml should also have a clause that runs the ExposeTask on the relevant classes, like this:

<taskdef name="expose" classname="org.python.expose.generate.ExposeTask">
<classpath>
<path refid="classpath" /> <pathelement path="${compile.dir}" />

System Message: WARNING/2 (<string>, line 182)

Definition list ends without a blank line; unexpected unindent.

</classpath>

System Message: WARNING/2 (<string>, line 183)

Definition list ends without a blank line; unexpected unindent.

</taskdef>

<expose srcdir="${compile.dir}"
destdir="${exposed.dir}" includesfile="${base.dir}/JythonExposed.includes"/>

(Taken from jython's build.xml.) You probably want to make your non-preprocessed classes (all the ones in JythonExposed.includes) get compiled someplace else, and only have the compiled ones show up in this "exposed" directory, so that you don't have two versions of each class on your classpath, and be careful if you're using an IDE.

PythonTypesInJava (last edited 2012-02-11 16:19:14 by JeffAllen)