5.0
Attributes, Variables, & Functions
5.1
Attributes
It's time to say it again. "Everything in Prothon is an object".
So what exactly is an object? We know objects hold binary data like
integers and strings. Creating objects that hold binary data can be
done only by C code modules and we won't cover those in this tutorial.
Objects can also hold other objects of any type. I'm not talking about
containers like lists and dictionaries. Those are specialized objects
just for containment. I'm talking about all objects. Every object, whether
it be an integer, string, or even a vanilla object with no type, can
hold other objects called attributes.
So
objects are things that can hold binary data and attributes. Attributes
are stored in an object much like dictionaries hold objects. They are
stored with a key, but the attribute's keys can't be just any old immutable
object as in a dictionary. In an attribute the key must be a legal Prothon
symbol, like "x", or "hasKey_?".
A
Prothon symbol can be any combination of the digits 0 to 9, the letters
a to z, A to Z, and the underbar ( _ ). It cannot start with a digit.
It may also optionally have a single exclamation mark ( ! ) or question
mark ( ? ) at the end.
If
you have an object stored in a variable, then you can access the attributes
of that object in a simple way using the period operator. If there is
an object in "x" and there is an object that is an attribute
of "x" with the key "y", then you can just say "y"
is an attribute of "x" and you can access it with "x.y".
Just
like dictionaries, you can add new attributes or replace an existing
one by simply using using an assignment statement. "x.y = 1"
will create a new attribute "y" or replace an old one. Also,
you can delete an attribute with the "del" delete command
just like dictionaries.
5.2
Variables, Scope, & With
When
you run a Prothon source file, there is a special object called a module
created. We will learn more about modules later. This module, called
"Main1", has attributes as all objects are capable of. It's
attributes are special in that they are the "variables" of
the code that runs at the main level of the code. So if the first line
of a source file says "x=1", you are really saying "Main1.x
= 1".
Every
variable in all Prothon code is an attribute of some object. So variables
and attributes are the same thing. When you use variables though, quite
often the object that the variable is an attribute of is hidden and
it sometimes takes special knowledge of the rules to figure out what
that object is. This object that holds variables is called a "scope
object" because it holds the variables for a certain scope of the
code. A scope is something like the main level, the inside of a function,
etc
Prothon
has a special "with" statement just to set the current scope
object. The "with" statement has the form "with name:
body". Name is the object that will temporarily become the scope
object. Body is a code body just like those in an "if" or
"for" statement, except that the body in a "with"
statement always executes just once. It is not conditional or a loop.
The "with" statement is convenient for when you are about
to work on one object extensively and want to save typing and to concentrate
on that object. Later you will see something "with" is very
useful for. Here is an example for now:
>>> x = [1,2] # x is an ordinary list object
[1, 2]
>>> with x:
... a = 'T' # add attribute a to object x
...
>>> print x.a # attr a is not related to list data [1,2]
T # list is stored in binary data of x
>>> # not in the attributes of x
5.3
Functions & the Def Statement
A function allows you to factor out code to reuse in multiple places
and to abstract a function into a higher-level concept to call.
Prothon
is a dynamic language, which means you define the functions at run-time,
not during compile time in advance. Once again, "everything is
an object" so functions are objects also. You create a function
object by using the "def statement. A function definition consists
of the code to run, the parameters to pass in when the function starts
and the value to return when the function ends.
The
def statement form is "def name(params): body". The name is
the variable that will hold the new function object being created, just
as if the name was on the left side of an assignment statement (name
= function). The body is the body of code that is executed just as in
the "if", "for", and other statements, except that
this code is not executed until the function is called. When the "def"
statement executes this block is just saved away for later. Params is
the list of variable names that the incoming parameters will be assigned
to when the function is first started. These are called the formal parameters
as opposed to the actual parameters, or arguments, that are in the function
call.
Function
calls look similar to the definition of a function. They have the form
"name(params)" where name is the variable holding the function
object and params are the actual parameters to be passed to the formal
parameters in the function definition. Let's look at some examples:
# Prothon source file tut8.pr
def func1(x):
print "x is",x
print func1 # func1 is an object that hasn't run
func1(99) # now it runs
This is the output of the program:
<func:93c120>
x is 99
Prothon
has some very sophisticated methods for matching the actual parameters
to the formal parameters. The simplest method is positional matching.
If func is defined as func(x,y,z) and is called as func(1,2,3) then
x = 1, y = 2, and z = 3 after the function starts.
The
next param passing trick is the use of default values, also known as
optional params. If the formal param in the function definition has
the form "name = value", then that param can be missing in
the actual params and the default value will be used instead. If the
func is defined as func(x, y = 11, z = 12) and it is called as func(1,
2) then x = 1, y = 2, and z = 12 inside the function. Note that the
formal params with defaults must follow the params without defaults
to avoid ambiguities.
The
third param technique is to match the formal and actual params by keyword
instead of by position. To do this, you use the form "name = value"
in the actual param in the function call. This allows you to place the
actual params in any order and it makes the call self-documenting. If
the func is defined as func(x, y, z) and it is called as func(y = 1,
z = 2, x = 3), then x = 3, y = 1, and z = 2 inside the function.
Finally,
there is a very powerful technique that allows a variable number of
parameters to be passed to or accepted into a function. If a formal
parameter list ends in "*var" then all actual positional parameters
"left over", that haven't been assigned to formal positional
parameters, are put into a list named var. Likewise, if the formal list
ends with a "**var" then all the given actual keyword parameters
that don't match any parameters in the formal list are put into a dictionary
named var with the name as the key and the actual value as the value.
If you put both "*var1" and "**var2" into the formal
list then all actual parameters are guaranteed to be captured.
In
the other direction, you can pass *var1 and **var2 as actual parameters
in a call where var is a list of actual positional parameters and var2
is a dictionary of actual keyword parameters. This allows you to calculate
an arbitrary number of parameters to pass to a function.
Note that combining these two directions allows you to accept every
possible set of parameters in a function by using the definition func1(*args,
**kwargs) and then passing on that arbitray set of parameters to another
function with the call func2(*args, **kwargs).
Here
are some samples of parameter passing:
# Prothon source file tut9.pr
def func(x,y=1,*args,**kwargs):
print 'x:',x,'y:',y
for arg in args:
print 'arg:',arg
for kwarg in kwargs.items():
print 'kwarg:', kwarg[0], kwarg[1]
print
func(0)
func(1,2)
func(1,x=11,y=22,z=33)
func(1,2,3,4)
func(1,2,3,4,a=11,b=22,c=33)
Here is the output:
x: 0 y: 1
x: 1 y: 2
x: 1 y: 22
kwarg: x 11
kwarg: z 33
x: 1 y: 2
arg: 3
arg: 4
x: 1 y: 2
arg: 3
arg: 4
kwarg: a 11
kwarg: b 22
kwarg: c 33
So
what happens to the parameters when they are passed in to the function?
When the function starts, a new scope object is created from scratch
called the "local scope". This is where the local variables
of the function are stored. After the local scope object is created,
the actual parameters are copied into that scope object just as if an
assignment statement happened for each parameter.
Let's say that the scope object is called "local" (it's not
really, it doesn't have a name). Then a function defined as func(a,
b, c) and called as func(1, 'a', obj) would cause parameter passing
that is equivalent to this code:
local.a = 1
local.b = 'a'
local.c = obj
Then inside the function, any reference to a, b or c will default to
the "local scope" and actually use local.a, local.b and local.c.
Notice
that the last parameter passed was an obj that might be mutable. Just
as in an assignment, the thing passed is just a reference to the obj,
not a copy of the object. This means that the variable obj outside of
the function is the same exact object as the c object inside the function.
If you make a change to c then obj will change also. This cannot happen
with a or b since numbers and strings are immutable and cannot be changed.
Function
always return a value to the caller. In the functions we have been looking
at they have been returning the default value of None. None is a special
unique object like the True and False objects. Speaking of True and
False, None.bool_() returns False so None is False in expressions and
"if" and "while" statements.
To
return something other than None, or to stop executing a function somewhere
other than at the end, you use the "return" statement. This
statement is somewhat like "break" in that it stops execution,
but "return" returns from the function block of code no matter
how deeply nested. If return has a parameter, that is returned as the
return value of the function, else the None object is returned.
>>> def squared(x):
... return x*x
...
>>> squared(3)
9
>>>
|