[ZDP] Python tree, delivery 2

Dody Gunawinata dody_g@eles.com
Thu, 07 Oct 1999 09:34:39 +1000


This is a multi-part message in MIME format.
--------------AF448765DA54E5CBB429B23D
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

Hi all,

Ok, this is a more complete Python sub-tree content. Fresh from oven :).
It's not terribly good yet,
as I aim for speed and accuracy, and not yet clarity. For clarity, I
would need to know what's needed. That's 
where feedback comes.

So, this one is for Jeff to chew on :) I promised him yesterday.

This includes the previous delivery (and a bit of patch to that section
as well).

I'll keep working on the rest of the 'untouched' topic in Python for the
rest of the week, so I guess, by the end of
next week, we will have Python content, that we can refine and redefine
again and again.

Btw, if you want to change some section/paragraph/code, just do it (and
make a note about it as well).

So when do I get my Pulitzer ? :P

Dody
--------------AF448765DA54E5CBB429B23D
Content-Type: text/plain; charset=us-ascii;
 name="Releasable.txt"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="Releasable.txt"

[todo-chapter intro-DG-99/10/2]


Variables

-No type declaration. 
-You can declare it anywhere. 
-Variable does not remember its type. 
-You assign value to a variable using '=' sign. 

It's important to note that Python does not use any special symbol for grouping statement. So there is no { } or begin end marker. It uses indentation to group statment. Wierd but don't worry, over the time you'll appreciate this feature. It forces you to 'pretty print' your source code from the start, which is a Good Thing(tm). You will see this rule in action in the next topic.

There are two ways to indent your code:
1. Uses tab consistently (4 or 8 spaces).
2. Use the good old spaces.

Either way it would work, however, never mix the use of the two methods together, because it would confuse the 
interpreter and generates error. So, tab only or spaces only.

There is no need to declare the type of your variable. And everything that starts with # is considered as a comment. It's a single line comment.

#Declare the variables
stringVariable = "test"
integerVariable = 24
floatingVariable = 34.3 

Use the print keyword to print the variable content to the screen. Yes, it's a language feature, not just a function. 

Notice that there is no special symbol or stuff like that in order to print the variable content.

print stringVariable
print integerVariable
print floatingVariable 

To 'chain' several variables in one single print statement, use the comma

print stringVariable, integerVariable, floatingVariable


You can mix displaying different type of variable in a single print statement. Notice that there is no extra space needed in the string because print would add it automatically if two different type are displayed in the same line. Smart eh?

print 'My first', stringVariable, "of Python script", integerVariable, floatingVariable 


Using print alone will give you a new line

print 


You can also do mathematical expression in the print statement.

print integerVariable, ' * 2 = ', integerVariable * 2 


As you might have noticed from the previous example, every print statement adds a new line. Use a comma (',') to prevent print adding a line break.

print "this sentence without ",
print "line breaks" 


Use the '=' to assign value from one variable to another.

anotherString = stringVariable 


Variable does not 'remember' its previous value's (implicit) type. So you can use one variable over and over again with different type of value. Use it with care.

stringVariable = floatingVariable 


You can use '' (single quotes) or "" (double quotes) for string literal. To display '' (single quote) in the string, enclose the string within double quotes mark. I prefer double quotes just for its consistency. I use single quotes in the previous examples just to let you know it can be done. After the following example, I will stay true to my double quotes.

print 'Single quote '
print " Double quote " 



ifs and elses


 if_stmt: "if" expression ":" suite          
 "elif" expression ":" suite)*          
 ["else" ":" suite] 

It supports the usual band of Boolean operators and some. They are 'and, or, not, ==, !=, <> (the same as !=, inequality), >, <, >=, <=' and some more. Let me again emphasise the fact that Python uses indentation for grouping statements. 

x = 30

if x > 20 :
   print "x is less than 20"
elif x == 20:
   print "x is equal 20"
   print "and not less than 20"
else:
   print "x is definitely larger than 20"
if x > 40 :
   print "like this one, larger than 40"
else :
   print "still larger then 20, but less or equal 40" 



While loops

 while_stmt: "while" expression ":" suite             
 ["else" ":" suite] 


Let's talk about some keywords exist just for the loop.

break as in C, Java or Object Pascal, breaks out from the current loop. 

continue continues with the next iteration of the loop. 

else clause in below example is optional. It only gets executed when the loop terminates normally through the exhaustion of the list (for loop) or when the condition become false (while loop), but not when the loop is terminated by break statement. So the following code will print 'Get to the end' after the numbers get printed. 
#Loop 10 times
x = 0
while x < 10:
   print x
   x = x + 1
else:
   print "Get to the end" 


And you know now that the string in the else clause on the following function will not get printed. You might as well remove the else clause. And let me remind you again, Python uses indentation for statement grouping. Notice as well that the else clause are in the same indentation as the while keyword.

#NOTICE that else: is at the same indentation
#as the while:
#Loop only 2 times

x = 0

while x < 10:
   if x >= 2:
      break 
   print x
   x = x + 1
else:
   print "This ain't getting printed" 


For loops


 for_stmt: "for" target_list "in" expression_list ":" suite           
 ["else" ":" suite] 

There is no concept of loop incrementing a variable from low to high or vice versa in Python. The for loop is designed to iterate on a list. You can define a list and let the for loop iterate over it, or better yet, call range() function to generate the list for you automatically so you can just use it. Just remember that the loop iterates for the total number of element in the list. [100,200] is a list. A loop would iterates twice on this list, just the same as [1,2]. [100,1,200] would result in 3 loops. See that there is no concept of low and high value. As you remember, the for loop also come with the optional else clause. It behaves the same with its while loop counterpart. Let's the loop iterates on your list.

a = ['a','b','c']
for x in a:
   print x #this prints a, b and c 


And check out the list the cool function range() generates for you.

#This is the result of a range function call
print range(3) #prints [0,1,2]
print range(-3,4) #prints [-3,-2,-1,0,1,2,3] 


And use it for your loop pleasure.

#the simple for loop..loop 10 times
for x in range(10):
   print x

#see the flexibility of range function here.
#This loops for 6 times.
for x in range(4,10):
   print x 


[todo-tuples-DG-99/10/2]


The good old function 

 	funcdef: "def" funcname "(" [parameter_list] ")" ":" suite 
	parameter_list: (defparameter ",")* ("*" identifier [, "**" identifier] | "**" identifier |                
	defparameter [","]) 
	defparameter: parameter ["=" expression] 
	sublist: parameter ("," parameter)* [","] 
	parameter: identifier | "(" sublist ")" 
	funcname: identifier 

Let me remind you again that Python uses indentation to group statements. So like other construct in Python, a function is defined by a def keyword, and statements belong to the function are just indented together.

This is the simplest form of function you can define in Python. Void function without any parameter. Notice the 'documentation string'. It's not required, but it's really a good habit to explain what your function do, and some documentation tools uses this documentation string to document your function automatically. Just like Java with its Javadoc.

#'void' function without any parameter
def voidFunction ():
   "This is known as 'documentation string'"
   print "voidFunction(): This function returns nothing"

#This is how you call parameterless function
voidFunction() 


I only tell you half the truth when I say you can create void function in Python. Your 'void' function actually returns a value, albeit a boring one, None. None in other language is known as null, Null, or Nul in other languages. Notice the capital N.

#'void' function actually returns 'None' value
print voidFunction() 


Now, let's create a function that actually returns a real value. As you see, there is no difference between the declaration of the next function with it's 'void' counterparts. The only thing that separates them is that below functions has return statement with value. So the value returned from the function actually depends on what you put on the return statement.


#This function actually returns something
def returnFunction():
"""However, this style with three double quotes
is recommended, as you see, multiple line
span"""
   i = 'returnFunction(): This string would be returned'
   return i


#see the result
print returnFunction() 


The return keyword actually can be used for another purpose, which is it enables you to exit the function immediately. It's useful for some situation. And you don't need to specify a value for the return keyword either. If you don't give a value, it returns None.

def returnNone():
"""Documentation is not required, but recommended so
some external tools can use it to document your function.
Just like JavaDoc"""
   return
   print "returnNone(): This doesn't get printed at all"

returnNone() #This function does nothing
print returnNone() #This would print 'None' 


And of course function in Python can accept arguments. Well, as you might have guessed, no type declaration needed either.


Notice the single line function. It's quite common to see Python source code with this kind of style for single statement function. I prefer to indent the single statement because it enables me to read faster.

#Function with parameter, no type is needed. Notice the
#single line function
def parameterFunction (First, Second): print First, ' ', Second

#Calling it
parameterFunction ("String arguments", 40)
parameterFunction (58.43, "it's float") 


As you see above, Python doesn't enforce the type at all. It's up to the programmer to be careful with passing arguments. Python does raise an error if the type is incompatible with the operation in the function, which is not the case with the above function.


This is another trick in Python for function. Default parameter.

def defaultFunction (MyName, Age = 21, MoneyLeft = 30.25) :
""""""
   print "My name is", MyName
   print "I am", Age, "years old "
   print "I have only", MoneyLeft, "in my pocket"

#Five wicked ways to call the function
print
defaultFunction ("John")
print
defaultFunction ("John",30)
print
defaultFunction ("John",30, 300.00)
print
defaultFunction ("John", MoneyLeft = 1000.00);
cpodeprint
defaultFunction (Age = 90, MyName = "John") 


Last but not the least, you can assign your function to another variable. Just like a function pointer (using C terms). Notice that you don't use the () argument mark, because doing so means that you are assigning value from the function instead.

anotherDefaultFunction = defaultFunction
anotherDefaultFunction ("John") 


[todo-list-DG-99/10/2]


Using external modules/libraries

  import string

Now you can use all the function in string module. Check the library reference that comes with your Python installation for more external function libraries.:

  string.split('Split;This',';')


You need to qualify them. I usually use this notation if I don't use the function often in my code. Otherwise, you can always use function aliasing ::

  split = string.split
  split('Split;This',';')


Another way is to use explicit function import:

  from string import split, find


This import the function explicitly so you can use without the module prefix.:

  split('Split;This',';') 


Off course you can just import the whole thing. Now you can use all the exportable function from string module without using module prefixes. Remember the word "exportable":

  from string import *

  split ('I want this;I want that',';')


Creating modules/libraries

Create your collection of functions and put them in a file. All functions defined in the file which names aren't started with _ prefix are exportable. To keep the function "private" in the module, add _ (underscore) prefix in front of the function name.:

  def PrintMyName():
        print "My Name"

  def PrintMyAge():
        print "Your age"

  def _PrintMe():
        print "This can't be used"


and save it as myLib.py (as an example). Then use it like below example. See that the file name becomes the module name. That applies for other libraries as well. The string library would be stored in a file called string.py. You can find it in your (python directory)/bin/lib directory.:

  import myLib

  myLib.PrintMyName()
  myLib.PrintMyAge()

  #This will result in error
  myLib._PrintMe()


[to do--search path in python-DG-99/10/2]


OO:Class and defining object

It is assumed that you have at least a basic understanding of object oriented programming. If not, the rest of this
OO discussion would be meaningless to you.

These are some of the Object Oriented programming characteristic and supports that comes with Python :

	-No explicit support for private, protected or public method and data member.

	-Inheritance, including Multiple Inheritance.

	-All methods are virtual.
	
	-Run time object modification. Sorry, I couldn't find a better terms for this. You will find out.

	-Create your object, and forget about it. The system takes care of it, just like Java (albeit with different mechanism, Python uses reference counting instead of garbage collector)

	-Polymorphism, well off course you get it, although it's different from what you might think.

	-Easy to use persistency support.

	-Generic object container that can contain objects of any kind.


Basic class



Many papers uses the terms class and class instance. I don't use them here because they can introduce some confusion, which is often the case with beginners. I'll use the term 'object' instead of 'class instance'. Class is well, a class -the base-, and object is what you call an instance of a class, i.e. that what you create from the class. By this we can say that an
object exists at run-time. There only exists one specific class, but there can exists multiple objects from a class. 

This is how you declare an empty class. The "pass" is a keyword which do nothing. You use it just to meet the requirements of a construct that requires at least one statement.:

  class Person :
        pass


This is the simplest form of class.

Then to be useful, you should make the object out of it (instantiate it):

  p = Person()


Off course the object does nothing at this point. Wait, you are about to see the "run time object modification" features of Python. Oops, you might wonder what it is. Python enables data members and methods to be added to  a 'live' objects
at run time (and deleted as well). Yes, that's why I use the term "run time object modification". So you can create
an object that does nothing, then add data member and methods to the "empty" object, and use it. This feature is missing
from other "traditional" OO programming language such as C++, Java, Eiffel or Object Pascal. 


Now you can declare a new variable for the object:

  p.Name = "John"
  p.Age  = 20


Notice that you are actually assigning a new variable to an object. Of course you can retrieve it again.:

  print "My name is",p.Name,"and my age is",p.Age


How about function ? Well, you can do it as well.:

  def Address()
        print "Omaha, Nebraska, USA"

  p.Address = Address

  #then use it as usual

  p.Address()


It would be the end of the world before Java allows you to do this. Imagine what you can do with this features. How about creating a class factory that keeps enhancing the currently live object by patching them with more and more data and attributes (or delete them)? Genetic programming anyone ? You can also use the class to act like record or structure to store data. Or as a more efficient variant records (Object Pascal) or union (C/C++).


Object assignment

Continuing the previous example, if you assign p to another variable, you are actually copying the reference to the object, not the copy of the object itself. The following example should make it clear:

  m = p

  m.Age = 30

  print p.Age,"is the same as",m.Age


Changing the Age attributes thru m change the object it points to. Because p points to the same object, off course the printed value is the same.


Copy by value

Python does not support copy by value in the language. But, no fear, there is a module which provides the facility for object copy. It's the creatively named copy module.

The module provides two type of copies :

Shallow Copy

This will create a new object and copy the valued of the copied object to this new object. However, if the copied object has a reference to another object (aggregation) , the new object would get the reference to this child object. So in the end, you would have two object that points to the same child object:

  class Parent :
        def __init__(self,name):
                self.name = name

        def HasChild (self,child):
                self.child = child

  class Child :
        def __init__(self,name):
                self.name = name

  A = Parent("John")
  A.HasChild (Child("David"))

  import copy

  B = copy.copy (A)

  B.name = "Marco"
  B.child.name = "Emmanuella"

  print B.name, 'has a child named',B.child.name
  print A.name, 'also has a child named',A.child.name  


Deep Copy 

Like shallow copy, it creates a new object and copies the value from the copied object to the new object. For child object, the child object is recreated for the new object. So in the end, the new object would get a child object of its own, instead of just getting the object reference. This way, you got two object with two child object:

  B = copy.deepcopy (A)

  B.name = "Marco"
  B.child.name = "Emmanuella"

  print B.name, 'has a child named',B.child.name
  print A.name, 'has a child of different name',A.child.name  


Proper class : constructor

This is how you declare constructor in Python classes.:

  class Person:
        def __init__(self):
                self.Name = 'Me'
                self.Age  = 20


I bet the first thing you ask is, what is self ? Python class requires every method to have at least one argument to pass the object itself implicitly. So if you use __init__() it will actually generates error. Python uses the first argument for passing the object itself to the methods. The convention is to use self as the name of this argument. You can use other name for this, but it would make life easier for you and others if you stick with the convention self.

In Python, there is no method overloading !

The statement self.Name and self.Age creates objects data member. In other words, this data member would be created exclusively for each object, as opposes to class data member, where the values are shared by all objects. Let's add some more new stuff.:

  class Person:
        def __init__(self,Name='Me',Age=20):
                self.Name = Name
                self.Age  = Age

        def AboutMe(self):
                print 'My name is',self.Name,'and I am',self.Age,'old'

        def SetName(self, Name):
                self.Name = Name

        def SetAge (self, Age):
                self.Age  = Age


So the class is more complete right now. Notice that we use default arguments for the constructor. Let's see how we use it:

  me  = Person('Dody',21)
  you = Person()

  me.AboutMe()
  you.AboutYou()

  you.SetName("Bobby Brown")
  you.SetAge (30)

  me.AboutMe()
  you.AboutMe()


You see that we don't actually pass something to the AboutMe although the methods has one argument. That's what I mean by Python uses the first argument implicitly to pass the object to the methods. So you can ignore it.

It's public

The SetAge and SetName method in Person class in the previous example is redundant. Why ? because the object data member Name and Age is public. So you can directly access it using, using the previous object creation. :

  me.Name = "John Kaster"
  me.Age  = 31

  me.AboutMe()


To make it private, use the __ (double underscores) trick. This trick also applies for method as well:

  class Person:
        def __init__(self,Name='Me',Age=20):
                self.__Name = Name
                self.__Age  = Age

        def AboutMe(self):
                print 'My name is',self.__Name,'and I am',self.__Age,'old'

        def SetName(self, Name):
                self.__Name = Name

        def SetAge (self, Age):
                self.__Age  = Age

        def __LieMyAge (Lie='5')
                self.__Age = self.__Age - Lie


Now, you cannot access the Name and Age data member directly, and that applies to LieMyAge as well. So this won't work:

  me = Person()
  me.__Name = 'Dody'
  me.__LieMyAge()
  me.AboutMe()


But off course it's all a scam, a deceit, a lie. Do you know what the __ does to your variable/methods ? Python interpreters actually do a name mangling with anything that has double underscores, in the form of _classname__var/function name. That's why "me.__Name" or "me.__LieMyAge()" doesn't work, because they don't exist as far as Python concerned. So below example will actually works.:

  me = Person()
  me._Person__Name = 'Dody'
  me._Person__LieMyAge()
  me.AboutMe()


Well, off course you need to be determined to access those attributes, but again it is all possible with Python. Being able to use it doesn't mean that you have to use it. Use your own judgement when it is appropriate.


Polymorphism

People would laugh if someone actually talk about Polymorphism without explaining inheritance first. But we can do this in Python due to the way Python handles variables. Python variable does not have a type, so the traditional concept of Polymorphism where a parent class variable can call it's children methods doesn't exist or make a difference. We can use one variable to hold any kind of objects. No matter what kind of object the variable is referring too, as long as the object has the method name called show, everything is well and OK. Object type is not important. Virtual methods makes no different in terms of Polymorphism in Python, but it does have an effect in inheritance.:

  class Monkey:
        def show(self):
                print "This is monkey"

  class Car:
        def show(self):
                print "This is a car"

  class House:
        def show(self):
                print "This is a house"

  #Create empty list
  list = []
  #Add objects to the list 
  list.append (Monkey())
  list.append (Car())
  list.append (House())

  #Iterates on the list
  for x in list:
        x.show()

  #The loop prints
  #"This is monkey"
  #"This is a car"
  #"This is a house"


Above example uses the usual algorithm other book use to demonstrate polymorphism. It works simpler in Python. However, you probably miss a subtle thing that the example show. It is that the list can hold three object of different type and call the methods without any typecasting at all. Generic object container, the holy grail of other OO languages. You can't this directly in C++, Object Pascal or even in Java. In Java at least you can use interface as the list type, which the objects have to support. In C++ or Object Pascal, the list has to be the parent type of those objects, which is more restrictive than an interface. Can you see what Python offers for the OO world ?

The statement x.show() will raise an exception if the current object referred by x does have show() method. No fear though, you can detect whether the object has a particular method by using the built in function hasattr(obj,name).:

  if hasattr(x,'show'):

  # or

  if hasattr(Monkey(),'show'):


You can use this before inserting the object into the list or just before executing the method show(). Notice you only insert the name, not the arguments (this is possible because there is no method overloading in Python).


[todo-Inheritance-DG-99/10/2]
[todo-MI-DG-99/10/2]
[todo-Persistency-DG-99/10/2]
--------------AF448765DA54E5CBB429B23D--