Prothon Tutorial

 Prothon Home Previous Page Tutorial Outline Next Page

6.4 Object Initialization

When you create a thousand rectangles, you don't want them to all have the same width and height. So it would be nice to have a convenient way to give them their dimensions when they are created. Prothon has provided a standard way of doing initialization of objects.

The form of creating an object that we gave earlier is incomplete. It is really "obj = Prototype(args)". Just as when calling a function, you can provide initialization parameters for object creation. For this to work your "Prototype" object or one of it's prototype parents must have a method called "init_" defined with the formal parameters defined that match the calling "args".

When "Prototype(args)" is called, first the new object is created with the parent prototype of "Prototype" and no attributes or binary data. Then the method "init_" is called with this new object as "self", or you could say "init_" is called on the new object. If "init_" has a return statement with a value, then that value becomes the final result of "Prototype(args)". If there is no return value in "init_", then the "self" object in "init_" becomes the final result (this is the most common case).

# Prothon source file tut11.pr

object Rectangle:
    width = 0
    height = 0
    
    def init_(width, height):
        self.width = width
        self.height = height
        
    def area():
        return self.width * self.height
        
rect1 = Rectangle( 5, 12)
rect2 = Rectangle(10, 17)
print rect1.width, rect1.height, rect1.area()
print rect2.width, rect2.height, rect2.area()

The output of this file is:

5 12 60 
10 17 170 

6.5 Sub-types

Just as classes in object-oriented programming have sub-classes, prototypes can have sub-types. A sub-type is simply an object that has a link to a prototype but adds new methods or redefines a method. Usually a sub-type is a narrowing of a category. For our example, we will use a square as a sub-type of a rectangle. A square is a rectangle but it has the extra restriction that width equals height.

>>> object Square(Rectangle):
...    def init_(width):
...       Rectangle.init_{self}(width, width)
...
>>>

This may not be the best way to implement this, but it's an interesting way. Note that when you initialize the square, it calls the rectangle initialization routine with both width and height actual parameters equal to the square's one width. Let's look at the call closely.

    Rectangle.init_{self}(width, width)

First of all, the call has the Rectangle's name at the beginning. This is to make sure it gets the method from the Rectangle's attributes and get's that version of the method that is expecting width and height.

Next notice the "{self}". If this wasn't there the self scope object would default to where the "init_" function came from, which would be the "Rectangle" object itself. So "init_" would be called on "Rectangle" instead of the new object that needs to be initialized. In general, whenever you are calling an explicit version of a method from an object (a "directed" call), like Rectangle.init_, you need to think about what you want the method "called on" and if it isn't that object, include the {obj} where obj is what you want it called on.

Finally, the arguments, (width, width) are just the normal actual argument that must match the formal parameters expected in the Rectangle.init_ version. Now we can use square:

>>> sq = Square(7)
>>> print sq.width, sq.area()
7 49
>>>

6.6 Changing Object Types

Now I'd like to show you something useful that no object oriented language can do. Most object-oriented languages will let you change an object instance's type from a sub-type to a parent type, called "upcasting". In other words go from a narrower case to a broader case. They will sometimes allow you to "upcast" and then "downcast" back to the original type. Prothon will let you create an original object and then change types in either direction, either "upcast" or "downcast".

In this example we will modify the Rectangle prototype to intelligently recognize squares and then change the Rectangle object type to the more specific type of Square.

Note several new things in this example. We are using the "with" statement to "re-open" a prototype (Rectangle) and modify an existing definition (init_) by replacing the old one.

We are using a method "setProto" which replaces all the existing prototype links in an object with one new one.

We are defining a special method called "str_" that is used by print and other commands to represent an object in text form. Whatever string object is returned by "str_" is what will be shown for that object.

In order to save typing, we'll run tut11.pr and use the console:

C:\Prothon\pr\tutorial>prothon -i tut11.pr
5 12 60
10 17 170
Prothon 0.1 Interactive Console, Build 492, May 17 2004 (Ctrl-D to exit)
>>> with Rectangle:
...    def init_(width, height):
...       self.width = width
...       self.height = height
...       if width == height:
...          self.setProto(Square)
...
>>> object Square(Rectangle):
...    def init_(width):
...       Rectangle.init_{self}(width, width)
...    def str_():
...       return '<Square:'+self.width+'>'
...
>>> sq1 = Rectangle(10, 10)  # Rectangle changes to Square here
>>> sq2 = Square(15)
>>> print sq1, sq1.area()
<Square:10> 100
>>> print sq2, sq2.area()
<Square:15> 225
>>>

6.7 Multiple Inheritance

Prothon allows the object to have multiple prototype links which means that it can inherit from multiple parents. This multiple inheritance feature is powerful and should be used carefully as it can cause trouble if not used properly. Prothon will search all the parents of all the links for an attribute, but the search order can be complex when an object has multiple links to start from. If different parents on different paths have the same attribute you may not get the one you expect.

Let's add a new prototype to our Rectangle example called Solid. This prototype has a "depth" attribute that makes our rectangles into three dimensional objects when added to them. It will also add a "volume" method that calculates the volume of the solid. Our goal is to be able to just add the Solid prototype to any Rectangle or Rectangle sub-type to make a solid object.

In this example we will add the Solid prototype to the Square we developed before to turn the square into a cube. Our code for the Square and Rectangle here is the same. We are just adding the Solid prototype and using it to make the new prototype Cube.

# Prothon source file tut12.pr

object Rectangle:
    width = 0
    height = 0
    
    def init_(width, height):
        self.width = width
        self.height = height
        
    def area():
        return self.width * self.height
        
object Square(Rectangle):
    def init_(width):
        Rectangle.init_{self}(width, width)

object Solid:
    depth = 0
    
    def init_(depth):
        self.depth = depth
    
    def volume():
        return self.area() * self.depth

object Cube(Square, Solid):     # Cube has two prototypes
    def init_(width):
        Square.init_{self}(width)
        Solid.init_{self}(width)

    def str_():
        return '<cube:'+self.width+'>'

cube = Cube(3)

print cube, cube.area(), cube.volume()

Here is the output:

<cube:3> 9 27
 Prothon Home Previous Page Tutorial Outline Next Page