Prothon Tutorial

 Prothon Home Previous Page Tutorial Outline Next Page

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
>>>
 Prothon Home Previous Page Tutorial Outline Next Page