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
|