Wiz Programming Tutorials

This Tutorial will show you how to:

  • make Jython modules containing useful functions and classes that you can re-use

  • subclass Jython classes from other Jython classes
Trust me: you want to know how to do this. Otherwise you'll wind up copying and pasting a bunch of code, which will make your life miserable as soon as you have to change all of it.

Modules let you share common code across multiple files, so you only have any given function or class defined in one place. This is a software-engineering practice that you don't want to learn the hard way!

Contents

Modules

In Jython, a module is just a file with a bunch of definitions in it. Definitions can be:
  • variables
  • functions
  • classes
Here's a minimal example of a Jython module:

<span class='string'>"""</span><br><span class='keyword'></span>A<span class='keyword'></span> <span class='keyword'></span>test<span class='keyword'></span> <span class='keyword'></span>module<span class='keyword'></span>.<br><span class='string'>"""</span><br><br><span class='keyword'>def</span> <span class='function'>hello_world</span>():<br>    <span class='keyword'></span>return<span class='keyword'></span> <span class='string'>"<span class='keyword'></span>Hello<span class='keyword'></span>, <span class='keyword'></span>world<span class='keyword'></span>!"</span>

This module just has one comment and one function. The function, hello_world, has no parameters, and simply returns the string "Hello, world!".

This module can be placed anywhere you want (in your Wiz directory tree), and can be named anything you want. You can put as many functions as you like in it. You can also put in classes and variables (although variables aren't really recommended.)

For our example, we're going to call the module testmodule.py, and we'll put it in the directory wiz/rhialto/py/test/, which you can do using FTP. (In your own wiz dir, of course.)

Creating Modules

To create testmodule, we stick the code above into a file called testmodule.py, and FTP it to wiz/rhialto/py/test/.

To make it an official module, usable by other Jython code, you have to do something a bit wacky: you have to put another file in the same directory called "__init__.py". Moreover, you have to put __init__.py files in every parent directory of the module, up to and including your wiz directory. So in this case, we actually have to upload two files:

  • wiz/rhialto/__init__.py
  • wiz/rhialto/py/__init__.py
  • wiz/rhialto/py/test/__init__.py
The __init__.py files can be empty.

This definitely clutters up your directories, but that's how Python works, and you don't want to go complaining about Python's design, or the highly irritable Python Community Mafia (which has nothing to do with Wyvern) will treat you with icy disdain for the remainder of your days. So remember: it's a great design. Besides, you can stick useful Python statements in those __init__.py files, such as 'print "I am an __init__.py file.".

Using Modules

To use a module, you just import it using the import statement. The import statement has several variations. You can import whole modules, or individual definitions from modules, and you can choose how to import the module into your local namespace.

Let's look at some examples. In the following examples, I'm using the Top Secret Wyvern Python Interpreter Console, which is hidden from all but Elder (and some Senior) Wizards because it's kinda dangerous to use it in a running game server.

This console lets me type in Python statments and evaluate them on the fly. You can pretty much achieve the same effect by writing a Jython object and executing it with the normal game mechanisms (such as cloning it, or casting if if it's a spell). By forcing you to stick it in a file, it helps prevent typos that will destroy the universe.

Example 1

>>> import wyvern
>>> wyvern.wiz.rhialto.py.test.testmodule.hello_world()
'Hello, world!'

In this example, I typed import wyvern and it added the top-level package "wyvern" to my script's namespace. That means I can use the name in my script.

After I've done that, I can type the next line, which specifies the entire path to the function, in all its lengthy long-ness. But it works!

There's a better way, though, in the next example.

Example 2

>>> import wyvern.wiz.rhialto.py.test.testmodule
>>> testmodule.hello_world()
'Hello, world!'

This version looks almost the same as the first one. There's a big difference, though - I only have to type the full path to the module in the import statement. After that, I can just say "testmodule.<whatever>" without having to type it all out. Nice.

Example 3

The third (and possibly most common) way to do it is:

>>>from wyvern.wiz.rhialto.py.test.testmodule import hello_world
>>>hello_world()
'Hello, world!'

(Incidentally, the Python keywords from and import are blue above to highlight the fact that they're Python keywords, and we use blue for keywords in all the HTML code samples in these Tutorials.)

In this last example, I'm importing a particular definition from my testmodule module. I'm just importing the hello_world function, and then I can invoke it directly anywhere in my script.

Example 4

Let's say I have multiple functions defined in testmodule, like so:

<span class='string'>"""</span><br><span class='keyword'></span>A<span class='keyword'></span> <span class='keyword'></span>test<span class='keyword'></span> <span class='keyword'></span>module<span class='keyword'></span>.<br><span class='string'>"""</span><br><br><span class='keyword'>def</span> <span class='function'>hello_world</span>():<br>    <span class='keyword'></span>return<span class='keyword'></span> <span class='string'>"<span class='keyword'></span>Hello<span class='keyword'></span>, <span class='keyword'></span>world<span class='keyword'></span>!"</span><br><br><span class='keyword'>def</span> <span class='function'>goodbye_world</span>():<br>    <span class='keyword'></span>return<span class='keyword'></span> <span class='string'>"<span class='keyword'></span>Goodbye<span class='keyword'></span>, <span class='keyword'></span>cruel<span class='keyword'></span> <span class='keyword'></span>world<span class='keyword'></span>!"</span>

I can import testmodule as in Example 1 or 2 above, and invoke the functions with "testmodule.<function>()", like so:

>>> import wyvern.wiz.rhialto.py.test.testmodule
>>> testmodule.hello_world()
'Hello, world!'
>>> testmodule.goodbye_world()
Goodbye, cruel world!'

I could also import the functions directly, like this:

>>> from wyvern.wiz.rhialto.py.test.testmodule import hello_world, goodbye_world
>>> hello_world()
'Hello, world!'
>>> goodbye_world()
Goodbye, cruel world!'

In other words, I can specify which names I want to import, separated by commas.

Finally, although it's considered bad style for various reasons, you can also import all of the names from a module by doing this:

>>> from wyvern.wiz.rhialto.py.test.testmodule import *
>>> hello_world()
'Hello, world!'
>>> goodbye_world()
Goodbye, cruel world!'

That will grab both functions. If you have 100 functions defined in the module, well, you'll have them all in your own namespace. It's a bad idea because the names can collide with other names from your own file, or from other modules, and you may get weird, difficult-to-track-down bugs in your code. So in Wyvern, just don't use import *.

Now we're ready to take a look at subclassing.

Subclasses

We've already shown you how to extend a Java class with your Jython class. We do it in just about every other tutorial. Take a look at the command_test.py example to see a Jython subclass of the Wyvern Java class wyvern.lib.classes.DynamicObject.

In addition to subclassing Java classes, you can also subclass other Jython classes. Jython has one distinct difference: you can extend more than one Jython superclass. For example, you can type:

class foo(A, B, C):

Where A, B and C are all Jython classes. (One of them is even allowed to be a Java class.) This feature is called "Multiple Inheritance", and can get you into trouble unless you know what you're doing, so you should ask a Wizard for advice before trying it.

Now we're ready to see a real example.

Minimal Subclassing Example

In this example, I'm going to define two classes, in different modules:
  1. First, I'll define a superclass called supertest. It will inherit from the Java class wyvern.lib.classes.DynamicObject, which is a convenient GameObject implementation that you can clone and carry around with you.

  2. Second, I'll define a subclass of supertest called subtest, in an entirely different module. Because it inherits from supertest, it also indirectly inherits from DynamicObject as its "grandparent" class.

I defined some simple, working Jython code in two files for this example. Take a look at the code, and we'll discuss it below.

file: wyvern/wiz/rhialto/python/testA/supertest.py:

<span class='string'>"""</span><br><span class='keyword'></span>Test<span class='keyword'></span> <span class='keyword'></span>of<span class='keyword'></span> <span class='keyword'></span>subclassing<span class='keyword'></span> <span class='keyword'></span>in<span class='keyword'></span> <span class='keyword'></span>Jython<span class='keyword'></span>.  <span class='keyword'></span>This<span class='keyword'></span> <span class='keyword'></span>module<span class='keyword'></span> <span class='keyword'></span>defines<span class='keyword'></span> <span class='keyword'></span>the<span class='keyword'></span> <span class='keyword'></span>Jython<span class='keyword'></span><br><span class='keyword'></span>superclass<span class='keyword'></span>, <span class='keyword'></span>which<span class='keyword'></span> <span class='keyword'></span>inherits<span class='keyword'></span> <span class='keyword'></span>from<span class='keyword'></span> <span class='keyword'></span>wyvern<span class='keyword'></span>.<span class='keyword'></span>lib<span class='keyword'></span>.<span class='keyword'></span>classes<span class='keyword'></span>.<span class='keyword'></span>DynamicObject<span class='keyword'></span>.<br><span class='string'>"""</span><br><br><span class='keyword'></span>from<span class='keyword'></span> <span class='keyword'></span>wyvern<span class='keyword'></span>.<span class='keyword'></span>lib<span class='keyword'></span>.<span class='keyword'></span>classes<span class='keyword'></span> <span class='keyword'></span>import<span class='keyword'></span> <span class='keyword'></span>DynamicObject<span class='keyword'></span><br><br><span class='keyword'>class</span> <span class='function'>supertest</span>(DynamicObject):<br><br>    <span class='keyword'>def</span> <span class='function'>initialize</span>(self):<br><br>        <span class='keyword'></span><span class='instance'>self</span><span class='keyword'></span>.<span class='keyword'></span>setProperty<span class='keyword'></span>(<span class='string'>"<span class='keyword'></span>short<span class='keyword'></span>"</span>, <span class='string'>"<span class='keyword'></span>supertest<span class='keyword'></span>"</span>)<br>        <span class='keyword'></span><span class='instance'>self</span><span class='keyword'></span>.<span class='keyword'></span>setIntProperty<span class='keyword'></span>('<span class='keyword'></span>value<span class='keyword'></span>', <span class='keyword'></span>10000<span class='keyword'></span>)<br>        <span class='keyword'></span><span class='instance'>self</span><span class='keyword'></span>.<span class='keyword'></span>setDefaultImage<span class='keyword'></span>('<span class='keyword'></span>objects<span class='keyword'></span>/<span class='keyword'></span>ruby_key<span class='keyword'></span>')<br><br>        <span class='keyword'></span><span class='instance'>self</span><span class='keyword'></span>.<span class='keyword'></span>super__initialize<span class='keyword'></span>()<br><br>    <span class='keyword'>def</span> <span class='function'>__repr__</span>(self):<br>        <span class='keyword'></span>return<span class='keyword'></span> <span class='keyword'></span><span class='instance'>self</span><span class='keyword'></span>.<span class='keyword'></span>toString<span class='keyword'></span>()

file: wyvern/wiz/rhialto/python/testB/subtest.py:

<span class='string'>"""</span><br><span class='keyword'></span>Jython<span class='keyword'></span> <span class='keyword'></span>subclassing<span class='keyword'></span> <span class='keyword'></span>test<span class='keyword'></span>.  <span class='keyword'></span>Defines<span class='keyword'></span> <span class='keyword'></span>the<span class='keyword'></span> <span class='keyword'></span>subclass<span class='keyword'></span>, <span class='keyword'></span>which<span class='keyword'></span> <span class='keyword'></span>inherits<span class='keyword'></span><br><span class='keyword'></span>from<span class='keyword'></span> <span class='keyword'></span>supertest<span class='keyword'></span>, <span class='keyword'></span>defined<span class='keyword'></span> <span class='keyword'></span>in<span class='keyword'></span> <span class='keyword'></span>another<span class='keyword'></span> <span class='keyword'></span>package<span class='keyword'></span>.<br><span class='string'>"""</span><br><br><span class='keyword'></span>from<span class='keyword'></span> <span class='keyword'></span>wyvern<span class='keyword'></span>.<span class='keyword'></span>wiz<span class='keyword'></span>.<span class='keyword'></span>rhialto<span class='keyword'></span>.<span class='keyword'></span>python<span class='keyword'></span>.<span class='keyword'></span>testA<span class='keyword'></span>.<span class='keyword'></span>supertest<span class='keyword'></span> <span class='keyword'></span>import<span class='keyword'></span> <span class='keyword'></span>supertest<span class='keyword'></span><br><br><span class='keyword'>class</span> <span class='function'>subtest</span>(supertest):<br><br>    <span class='keyword'>def</span> <span class='function'>initialize</span>(self):<br><br>        <span class='keyword'></span><span class='instance'>self</span><span class='keyword'></span>.<span class='keyword'></span>super__initialize<span class='keyword'></span>()<br><br>        <span class='keyword'></span><span class='instance'>self</span><span class='keyword'></span>.<span class='keyword'></span>setImage<span class='keyword'></span>('<span class='keyword'></span>objects<span class='keyword'></span>/<span class='keyword'></span>wooden_key<span class='keyword'></span>')<br>        <span class='keyword'></span><span class='instance'>self</span><span class='keyword'></span>.<span class='keyword'></span>setProperty<span class='keyword'></span>('<span class='keyword'></span>short<span class='keyword'></span>', '<span class='keyword'></span>subtest<span class='keyword'></span>!')<br>        <span class='keyword'></span><span class='instance'>self</span><span class='keyword'></span>.<span class='keyword'></span>setIntProperty<span class='keyword'></span>('<span class='keyword'></span>value<span class='keyword'></span>', <span class='keyword'></span>666<span class='keyword'></span>)

Take a look at the code and see if you can figure out what's going on. We'll explain it below.

Example Explained

OK, so what did we do in the previous example?

First, we defined supertest.py, which contains a single definition: a class named supertest. This might be a little confusing to you, since the module and the class don't have to have the same name. We do that a lot in Wyvern because it's sort of consistent with the way Java classes are defined.

Class supertest sets a few properties on itself in its initialize() method, to customize its appearance, description, and shop value. Then it calls its superclass's initialize() method, which will finish initializing all of the default properties.

Note that supertest also overrides the Jython __repr__ method, which is Jython's version of Java's toString() method. The Jython designers have been asked many, many times to have the __repr__ method automatically invoke toString(), but they're still, um, considering it. So if you write 600 Jython classes, you have to write 600 __repr__ stub functions. We're working on creating a mixin class that will take care of many hiding of Jython's warts from you, but it's not ready yet, since we're also working on about 1000 other things.

Second, we defined the subtest class. Python does have the nice advantage (over Java) that you don't actually put the name of the module inside its file, meaning you can easily move it between directories or rename the file. Sort of easily, that is - you still have to go through all the files that import your module and fix their import statements. So it's best to choose a directory structure that will scale well as you add dozens or (potentially) hundreds of modules.

Note that subtest doesn't need a __repr__ method, because it inherits it from its superclass.

Note also that subtest is a descendant of DynamicObject, which means you can clone it using the clone command, which sticks it in your inventory.

I tried this, and cloning wiz/rhialto/python/testA/supertest.py got me a ruby key called "supertest", while cloning wiz/rhialto/python/testB/subtest.py got me a wooden key called "subtest". Worked like a charm!

If you have questions or feedback about this module, please send us email. The easiest approach is probably to mail Rhialto from inside the game, in one of the post offices.

<< Previous Chapter Next Chapter >>