5.4
Self, Methods, & Binding
Just
as every function has a hidden "local" scope object whose
attributes are the local variables of the function, every function has
another hidden scope object called "self". You have seen "self"
in action without knowing it. When a function is "called on an
object" such as when a sort function is called on a list in "list.sort!()",
the list is the "self" scope object inside the "sort!"
function when it is executing.
The
local variables default to the "local" scope object whenever
you use any normal variable name like "x", but to access the
"self" variables you must use the prefix keyword "self"
as in "self.x". You may also access the "self" object
itself just by saying "self".
There
are a number of ways to specify the "self" scope object for
a function call and we will wait until later to cover some of them.
As you saw in the "list.sort!()" example, calling a function
on an object will set the self scope object by default. The simplest
way to call a function on an object is to make the function an attribute
of the object. You can do this in the "def" statement itself
or by assignment:
>>> x = ['Hello', 'World']
['Hello', 'World']
>>> def x.func():
... print self[0]
...
>>> x.func()
Hello
>>> def func2():
... print self[1]
...
>>> x.y = func2
<func:93e220>
>>> x.y()
World
>>>
You
can also specify the "self" scope object explicitly in the
function call by using the form "func{selfObj}(args)". This
will call the function "func" with the actual parameters "args"
and set the "self" scope object to "selfObj". You
might be tempted to think this is like one more actual parameter being
passed to the function but it is not. Parameters are assigned into local
variables that are attributes of the "local" scope object,
but the "self" is a scope object of its own.
>>> def func(index):
... if (index == -1):
... index = self.defIndex
... print self[index]
...
>>> x = ['Hello', 'World']
['Hello', 'World']
>>> x.defIndex = 0
0
>>> func{x}(0)
Hello
>>> func{x}(1)
World
>>> func{x}(-1)
Hello
>>>
In
object-oriented languages that have classes, there are things called
"methods". A method is a function attached to a class. Since
Prothon doesn't have classes, Prothon doesn't have any difference between
a method and a function. For reasons that you will learn later, Prothon's
functions that have references to "self" inside are similar
to methods. So Prothon has a terminology convention of calling any function
that has the keyword "self" inside a "method". Don't
forget that there is no difference between how a Prothon method and
a Prothon function works, it it just two words for the same thing. Also,
don't forget that all Prothon functions have the "self" scope
object, not just Prothon methods. It is just that only Prothon methods
use that "self" scope object.
There
are times when it is convenient to have a function object (method) with
a "self" object permanently attached for future use. This attaching
of the "self" object is called "binding" and the
combined object is called a "bound method". One usage example
of a bound method is a "callback" method. This is where you
send a bound method as an argument in a call to a remote function and
when some event occurs, that remote function invokes that callback method
as a signal.
To
make a bound method use this form "boundMethod = method{selfObj}".
Note that this is exactly like the call without the parentheses. The
"method" will be bound to "selfObj" to become the
"boundMethod". Now "boundMethod" can be called like
a normal function (method) and it will use the "selfObj" that
is bound inside. Note that specifying a different "self" as
in "boundMethod{anotherSelf}()" will cause an exception.
>>> x = ['Hello', 'World']
['Hello', 'World']
>>> def func(index):
... print self[index]
...
>>> boundMethod = func{x}
<func:939f00:bound:['Hello', 'World']>
>>> boundMethod(0)
Hello
>>> a = boundMethod
<func:939f00:bound:['Hello', 'World']>
>>> a(1)
World
>>>
5.5
Expressions
We
have been using expressions all along, such as when we say 1+2, but
how can we say "everything is an object" when an expression
looks nothing like an object? The answer is that expressions are made
up of operators like + which are actually functions in disguise and
we now know that functions are objects. The Prothon interpreter (actually
the parser) quickly changes the simple expression "x = 1+2"
into the Prothon statement "x = 1.add_(2)" by replacing the
addition operator + into ".add_()".
Numbers
(which are objects of course) can have functions (methods) called on
them. Note that in these binary operators the left side of the operator
is always the "self" object and the right side is a parameter:
>>> 1.add_(2) # same as 1 + 2
3
>>>
We
now know why floating point numbers cannot end with a decimal point
like "1." and that you must add a zero so that it becomes
"1.0". If you ended it with the period it would get confused
with the period that seperates numbers from function names.
You
might wonder why the add_ function has the underbar symbol ( _ ) hanging
precariously off the end of the name. This is a special convention in
Prothon that means the function is for internal use and not normally
to be seen. It also allows the Prothon programmer to use the function
name "add" and not accidently replace the internal version,
which might cause problems. This means that you should not create function
names that end with an underbar, unless you know what you are doing.
We will cover one or two special exceptions later.
Here
are the Prothon operators:
+ * These work on numbers, strings, and lists
- Subtract (binary operator) or negative (unary operator)
/ // The single / returns a float and // returns an integer
% ** % is modulo, ** means "raise to the power of"
<< >> Left shift and right shift
~ & | ^ Bitwise negate, and, or, & exclusive or (xor)
< <= > >= != == Comparison
"is" "is not" Identity (exact same object)
"in" "not in" Membership (left is in right collection)
"not" "and" "or" Boolean
Here
are some sample expressions:
>>> 12/5
2.4
>>> 12//5
2
>>> 12%5
2
>>> 3 < 4
True
>>> 3 is 3
False
>>> x = 3
3
>>> y = x
3
>>> x is y
True
>>> 3 in [1,2,3]
True
Several
of the operators have special behaviour worth noting. The comparison
operators can be chained together in an intuitive way. "1 <
2 < 3" would normally not work in the intuitive way because
it would be interpreted as "(1 < 2) < 3" which would
evaluate to "True < 3" which would give an error. Prothon
goes to the trouble to change "1 < 2 < 3" to the expression
"(1 < 2) and (2 < 3)" which is the accurate intuitive
meaning. Note that if you have "f1() > f2() > f3()"
that f2() is called only once even though it is changed to "(f1()
> f2()) and (f2() > f3())".
The
boolean operators "and" and "or" have the special
behaviour that only part of the expression is evaluated if not all of
it is needed. For example, while evaluating the expression "True
or func()" func() will not be called because the result of the
"or" is already known when the True is seen on the left side.
This is sometimes called "short-circuiting". Likewise, in
the expression "False and func()", func() will not be called.
Another
feature of "and" and "or" is that when the result
of the expression is "true", the actual result object will
be one of the inputs (remember that most objects evaluate to True).
This allows "gating" of values. The result of "True and
99" is 99. The result of "False or 'abc'" is 'abc'. This
allows you to do this trick, which should look familiar to you C programmers
as the "x ? y : z" construct (except that a and b are both
evaluated first):
# Prothon source file tut10.pr
def sel(q, a, b):
return (q and a) or ((not q) and b)
print sel(True, 1, 2)
print sel(False, 1, 2)
Which prints out this result:
1
2
(Note: Unfortunately a bug that was not fixed until build 554 prevents
this example from working in build 532. You may download the latest
build to run this example but many other examples may not work as the
language is changing and this tutorial may not be up-to-date. See the
download section on the Prothon home page for unstable build download
information).
|