⇤ ← Revision 1 as of 2007-12-24 06:16:31
Size: 10325
Comment:
|
Size: 10395
Comment:
|
Deletions are marked like this. | Additions are marked like this. |
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 classes 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 | ``org.python.expose.ExposedType`` annotation on the actual class to indicate that |
Line 32: | Line 31: |
with `@ExposedType` and just sets up the type hierarchy in Python. If | with ``@ExposedType`` and just sets up the type hierarchy in Python. If |
Line 37: | Line 36: |
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 40: |
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 43: |
`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 50: |
`@ExposedMethod`. `@ExposedMethod` can be applied to non-static public, | ``@ExposedMethod``. ``@ExposedMethod`` can be applied to non-static public, |
Line 53: | Line 52: |
processor encounters an `@ExposedMethod`, it generates a new inner class | processor encounters an ``@ExposedMethod``, it generates a new inner class |
Line 61: | Line 60: |
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 63: |
must take the generic `__call__` arguments, `PyObject[], String[]`. For | must take the generic ``__call__`` arguments, ``PyObject[], String[]``. For |
Line 76: | Line 75: |
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 79: |
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 86: |
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`` has an exposed method ``int___add__`` which appears in its type dict as ``__add__``. If the method name doesn't start with |
Line 91: | Line 90: |
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 later |
Line 104: | Line 103: |
the String `null` produces a Java null | the String ``null`` produces a Java null |
Line 106: | Line 105: |
the String `Py.None` produces the Python None value | the String ``Py.None`` produces the Python None value |
Line 110: | Line 109: |
if the method takes a boolean in the default's position, `true` or `false` can be specified | if the method takes a boolean in the default's position, ``true`` or ``false`` can be specified |
Line 115: | Line 114: |
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 117: |
normal coercion directly. `BINARY` indicates that the method is a binary operation like `__add__` or `__sub__`. For these types, the descriptor checks |
normal coercion directly. ``BINARY`` indicates that the method is a binary operation like ``__add__`` or ``__sub__``. For these types, the descriptor checks |
Line 121: | Line 120: |
The `CMP` type is only for `__cmp__` methods. If used, it checks if the method returns `-2`, and if so, raises a TypeException. |
The ``CMP`` type is only for ``__cmp__`` methods. If used, it checks if the method returns ``-2``, and if so, raises a TypeException. |
Line 128: | Line 127: |
different aspect of accessing the field. `@ExposedGet` takes care of read | different aspect of accessing the field. ``@ExposedGet`` takes care of read |
Line 133: | Line 132: |
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 134: |
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 136: |
`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. 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 |
Line 149: | Line 148: |
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 151: |
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 153: |
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 158: |
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 161: |
`org.python.core.PyOverridableNew` and the method annotated with `@ExposedNew` | ``org.python.core.PyOverridableNew`` and the method annotated with ``@ExposedNew`` |
Line 166: | Line 165: |
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 172: | Line 171: |
for an example of this type of `@ExposedNew`. | for an example of this type of ``@ExposedNew``. |
Line 174: | Line 173: |
With either form, there can be only one `@ExposedNew` per class. | With either form, there can be only one ``@ExposedNew`` per class. |
How to Expose Python Types from Java
Contents
Introduction
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 fields and methods that will be visible on the resulting Python type. A bytecode processor then adds code to the compiled classes 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 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.
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 @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 it's being called from a method descriptor, and Python method descriptors are available on types regardless of their location in the inheritance hierarchy, its 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.
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 argument into a String, boolean or int if the method takes that type. If the PyObject can't be coerced to that type, a TypeException will be raised. 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.
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 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 has an exposed method int___add__ which 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 later 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 argumetns 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 four types of values:
- null
- the String null produces a Java null
- Py.None
- 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
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 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. The CMP type is only for __cmp__ methods. If used, it checks if the method returns -2, and if so, raises a TypeException.
Exposing fields with @ExposedGet, @ExposedSet and @ExposedDelete
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-primitive value or to a non-primitive field. If on a method, that method will 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. 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 annotation, or if that isn't specified the name is taken directly from the name of the field or the 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. 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.