--- a/basic_python/intro.rst Wed Sep 09 14:39:47 2009 +0530
+++ b/basic_python/intro.rst Tue Sep 15 17:02:07 2009 +0530
@@ -91,7 +91,8 @@
appearance might differ based on the version of Python being used. The ``>>>``
thing shown is the python prompt. When something is typed at the prompt and the
enter key is hit, the python interpreter interprets the command entered and
-performs the appropriate action.
+performs the appropriate action. All the examples presented in this document are
+to be tried hands on, on the interactive interpreter.
::
@@ -196,6 +197,11 @@
This example is to show that unlike in C or C++ there is no limit on the
value of an integer.
+Try this on the interactive interpreter:
+``import this``
+
+*Hint: The output gives an idea of Power of Python*
+
*ipython* - An enhanced interactive Python interpreter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -277,7 +283,7 @@
print gcd(72, 92)
To run the script, open the shell prompt, navigate to the directory that
-contains the python file and run `python <filename.py>` at the prompt ( in this
+contains the python file and run ``python <filename.py>`` at the prompt ( in this
case filename is gcd.py )
**Running the python script**
@@ -289,7 +295,7 @@
Another method to run a python script would be to include the line
-`#! /usr/bin/python`
+``#! /usr/bin/python``
at the beginning of the python file and then make the file executable by
@@ -340,7 +346,7 @@
The only distinction comes during type checking (which is not a healthy practice).
Long numbers are tucked with a trailing 'L' just to signify that they are long.
Notice that in the example just lng at the prompt displays the value of the variable
-with the 'L' whereas `print lng` displays without the 'L'. This is because print
+with the 'L' whereas ``print lng`` displays without the 'L'. This is because print
formats the output before printing. Also in the example, notice that adding an
integer to a long does not give any errors and the result is as expected. So for
all practical purposes longs can be treated as ints.
@@ -409,3 +415,566 @@
Strings
~~~~~~~
+Strings are one of the essential data structures of any programming language.
+The ``print "Hello, World!"`` program was introduced in the earlier section, and
+the *"Hello, World!"* in the print statement is a string. A string is basically
+a set of characters. Strings can be represented in various ways shown below:
+
+::
+
+ s = 'this is a string' # a string variable can be represented using single quotes
+ s = 'This one has "quotes" inside!' # The string can have quotes inside it as shown
+ s = "I have 'single-quotes' inside!"
+ l = "A string spanning many lines\
+ one more line\
+ yet another" # a string can span more than a single line.
+ t = """A triple quoted string does # another way of representing multiline strings.
+ not need to be escaped at the end and
+ "can have nested quotes" etc."""
+
+Try the following on the interpreter:
+``s = 'this is a string with 'quotes' of similar kind'``
+
+**Exercise: How to use single quotes within single quotes in a string as shown
+in the above example without getting an error?**
+
+String operations
+-----------------
+
+A few basic string operations are presented here.
+
+**String concatenation**
+String concatenation is done by simple addition of two strings.
+
+::
+
+ >>> x = 'Hello'
+ >>> y = ' Python'
+ >>> print x+y
+ Hello Python
+
+*Try this yourself:*
+
+::
+
+ >>> somenum = 13
+ >>> print x+somenum
+
+The problem with the above example is that here a string variable and an integer
+variable are trying to be concantenated. To obtain the desired result from the
+above example the str(), repr() and the `` can be used.
+
+**str()** simply converts a value to a string in a reasonable form.
+**repr()** creates a string that is a representation of the value.
+
+The difference can be seen in the example shown below:
+
+::
+
+ >>> str(1000000000000000000000000000000000000000000000000L)
+ '1000000000000000000000000000000000000000000000000'
+ >>> repr(1000000000000000000000000000000000000000000000000L)
+ '1000000000000000000000000000000000000000000000000L'
+
+It can be observed that the 'L' in the long value shown was omitted by str(),
+whereas repr() converted that into a string too. An alternative way of using
+repr(value) is ```value```.
+
+A few more examples:
+::
+
+ >>> x = "Let's go \nto Pycon"
+ >>> print x
+ Let's go
+ to Pycon
+
+In the above example, notice that the '\n'(newline) character is formatted and
+the string is printed on two lines. The strings discussed until now were normal
+strings. Other than these there are two other types of strings namely, raw strings
+and unicode strings.
+
+**Raw strings** are strings which are unformatted, that is the backslashes(\) are
+not parsed and are left as it is in the string. Raw strings are represented with
+an 'r' at the start of a string.
+Let us look at an example
+
+::
+
+ >>> x = r"Let's go \nto Pycon"
+ >>> print x
+ Let's go \nto Pycon
+
+Note: The '\n' is not being parsed into a new line and is left as it is.
+
+*Try this yourself:*
+
+::
+
+ >>> x = r"Let's go to Pycon\"
+
+**Unicode strings** are strings where the characters are Unicode characters as
+opposed to ASCII characters. Unicode strings are represented with a 'u' at the
+start of the string.
+Let us look at an example:
+
+::
+
+ >>> x = u"Let's go to Pycon!"
+ >>> print x
+ Let's go to Pycon!
+
+Boolean
+~~~~~~~
+
+Python also provides special Boolean datatype. A boolean variable can assume a
+value of either *True* or *False* (Note the capitalizations).
+
+Let us look at examples:
+
+::
+
+ >>> t = True
+ >>> f = not t
+ >>> print f
+ False
+ >>> f or t
+ True
+ >>> f and t
+ False
+
+The **while** loop
+~~~~~~~~~~~~~~~~~~
+
+The Python **while** loop is similar to the C/C++ while loop. The syntax is as
+follows:
+
+::
+
+ statement 0
+ while condition:
+ statement 1 #while block
+ statement 2 #while block
+ statement 3 #outside the while block.
+
+Let us look at an example:
+
+::
+
+ >>> x = 1
+ >>> while x <= 5:
+ ... print x
+ ... x += 1
+ ...
+ 1
+ 2
+ 3
+ 4
+ 5
+
+The **if** conditional
+~~~~~~~~~~~~~~~~~~~~~~
+
+The Python **if** block provides the conditional execution of statements.
+If the condition evaluates as true the block of statements defined under the if
+block are executed.
+
+If the first block is not executed on account of the condition not being satisfied,
+the set of statements in the **else** block are executed.
+
+The **elif** block provides the functionality of evaluation of multiple conditions
+as shown in the example.
+
+The syntax is as follows:
+
+::
+
+ if condition :
+ statement_1
+ statement_2
+
+ elif condition:
+ statement_3
+ statement_4
+ else:
+ statement_5
+ statement_6
+
+Let us look at an example:
+
+::
+
+ >>> n = raw_input("Input a number:")
+ >>> if n < 0:
+ print n," is negative"
+ elif n > 0:
+ print n," is positive"
+ else:
+ print n, " is 0"
+
+**raw_input()**
+~~~~~~~~~~~~~~~
+
+In the previous example we saw the call to the raw_input() subroutine.
+The **raw_input()** method is used to take user inputs through the console.
+Unlike **input()** which assumes the data entered by the user as a standard python
+expression, **raw_input()** treats all the input data as raw data and converts
+everything into a string. To illustrate this let us look at an example.
+
+::
+
+ >>> input("Enter a number thats a palindrome:")
+ Enter a number thats a palindrome:121
+ 121
+
+ >>> input("Enter your name:")
+ Enter your name:PythonFreak
+ Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+ File "<string>", line 1, in <module>
+ NameError: name 'PythonFreak' is not defined
+
+As shown above the **input()** assumes that the data entered is a valid Python
+expression. In the first call it prompts for an integer input and when entered
+it accepts the integer as an integer, whereas in the second call, when the string
+is entered without the quotes, **input()** assumes that the entered data is a valid
+Python expression and hence it raises and exception saying PythonFreak is not
+defined.
+
+::
+
+ >>> input("Enter your name:")
+ Enter your name:'PythonFreak'
+ 'PythonFreak'
+ >>>
+
+Here the name is accepted because its entered as a string (within quotes). But
+its unreasonable to go on using quotes each time a string is entered. Hence the
+alternative is to use **raw_input()**.
+
+Let us now look at how **raw_input()** operates with an example.
+
+::
+
+ >>> raw_input("Enter your name:")
+ Enter your name:PythonFreak
+ 'PythonFreak'
+
+Observe that the **raw_input()** is converting it into a string all by itself.
+
+::
+
+ >>> pal = raw_input("Enter a number thats a palindrome:")
+ Enter a number thats a palindrome:121
+ '121'
+
+Observe that **raw_input()** is converting the integer 121 also to a string as
+'121'. Let us look at another example:
+
+::
+
+ >>> pal = raw_input("Enter a number thats a palindrome:")
+ Enter a number thats a palindrome:121
+ >>> pal + 2
+ Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+ TypeError: cannot concatenate 'str' and 'int' objects
+ >>> pal
+ '121'
+
+Observe here that the variable *pal* is a string and hence integer operations
+cannot be performed on it. Hence the exception is raised.
+
+**int()** method
+~~~~~~~~~~~~~~~~
+
+Generally for computing purposes, the data used is not strings or raw data but
+on integers, floats and similar mathematical data structures. The data obtained
+from **raw_input()** is raw data in the form of strings. In order to obtain integers
+from strings we use the method **int()**.
+
+Let us look at an example.
+
+::
+
+ >>> intpal = int(pal)
+ >>> intpal
+ 121
+
+In the previous example it was observed that *pal* was a string variable. Here
+using the **int()** method the string *pal* was converted to an integer variable.
+
+*Try This Yourself:*
+
+::
+
+ >>> stringvar = raw_input("Enter a name:")
+ Enter a name:Guido Van Rossum
+ >>> stringvar
+ 'Guido Van Rossum'
+ >>> numvar = int(stringvar)
+
+
+Functions in Python: **def**
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+*Functions* allow us to enclose a set of statements and call the function again
+and again instead of repeating the group of statements everytime. Functions also
+allow us to isolate a piece of code from all the other code and provides the
+convenience of not polluting the global variables.
+
+*Function* in python is defined with the keyword **def** followed by the name
+of the function, in turn followed by a pair of parenthesis which encloses the
+list of parameters to the function. The definition line ends with a ':'. The
+definition line is followed by the body of the function intended by one block.
+The *Function* must return a value::
+
+ def factorial(n):
+ fact = 1
+ for i in range(2, n):
+ fact *= i
+
+ return fact
+
+The code snippet above defines a function with the name factorial, takes the
+number for which the factorial must be computed, computes the factorial and
+returns the value.
+
+A *Function* once defined can be used or called anywhere else in the program. We
+call a fucntion with its name followed by a pair of parenthesis which encloses
+the arguments to the function.
+
+The value that function returns can be assigned to a variable. Let's call the
+above function and store the factorial in a variable::
+
+ fact5 = factorial(5)
+
+The value of fact5 will now be 120, which is the factorial of 5. Note that we
+passed 5 as the argument to the function.
+
+It may be necessary to document what the function does, for each of the function
+to help the person who reads our code to understand it better. In order to do
+this Python allows the first line of the function body to be a string. This
+string is called as *Documentation String* or *docstring*. *docstrings* prove
+to be very handy since there are number of tools which can pull out all the
+docstrings from Python functions and generate the documentation automatically
+from it. *docstrings* for functions can be written as follows::
+
+ def factorial(n):
+ 'Returns the factorial for the number n.'
+ fact = 1
+ for i in range(2, n):
+ fact *= i
+
+ return fact
+
+An important point to note at this point is that, a function can return any
+Python value or a Python object, which also includes a *Tuple*. A *Tuple* is
+just a collection of values and those values themselves can be of any other
+valid Python datatypes, including *Lists*, *Tuples*, *Dictionaries* among other
+things. So effectively, if a function can return a tuple, it can return any
+number of values through a tuple
+
+Let us write a small function to swap two values::
+
+ def swap(a, b):
+ return b, a
+
+ c, d = swap(a, b)
+
+Function scope
+---------------
+The variables used inside the function are confined to the function's scope
+and doesn't pollute the variables of the same name outside the scope of the
+function. Also the arguments passed to the function are passed by-value if
+it is of basic Python data type::
+
+ def cant_change(n):
+ n = 10
+
+ n = 5
+ cant_change(n)
+
+Upon running this code, what do you think would have happened to value of n
+which was assigned 5 before the function call? If you have already tried out
+that snippet on the interpreter you already know that the value of n is not
+changed. This is true of any immutable types of Python like *Numbers*, *Strings*
+and *Tuples*. But when you pass mutable objects like *Lists* and *Dictionaries*
+the values are manipulated even outside the function::
+
+ >>> def can_change(n):
+ ... n[1] = James
+ ...
+
+ >>> name = ['Mr.', 'Steve', 'Gosling']
+ >>> can_change(name)
+ >>> name
+ ['Mr.', 'James', 'Gosling']
+
+If nothing is returned by the function explicitly, Python takes care to return
+None when the funnction is called.
+
+Default Arguments
+-----------------
+
+There may be situations where we need to allow the functions to take the
+arguments optionally. Python allows us to define function this way by providing
+a facility called *Default Arguments*. For example, we need to write a function
+that returns a list of fibonacci numbers. Since our function cannot generate an
+infinite list of fibonacci numbers, we need to specify the number of elements
+that the fibonacci sequence must contain. Suppose, additionally, we want to the
+function to return 10 numbers in the sequence if no option is specified we can
+define the function as follows::
+
+ def fib(n=10):
+ fib_list = [0, 1]
+ for i in range(n - 2):
+ next = fib_list[-2] + fib_list[-1]
+ fib_list.append(next)
+ return fib_list
+
+When we call this function, we can optionally specify the value for the
+parameter n, during the call as an argument. Calling with no argument and
+argument with n=5 returns the following fibonacci sequences::
+
+ fib()
+ [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
+ fib(5)
+ [0, 1, 1, 2, 3]
+
+Keyword Arguments
+-----------------
+
+When a function takes a large number of arguments, it may be difficult to
+remember the order of the parameters in the function definition or it may
+be necessary to pass values to only certain parameters since others take
+the default value. In either of these cases, Python provides the facility
+of passing arguments by specifying the name of the parameter as defined in
+the function definition. This is known as *Keyword Arguments*.
+
+In a function call, *Keyword arguments* can be used for each argument, in the
+following fashion::
+
+ argument_name=argument_value
+ Also denoted as: keyword=argument
+
+ def wish(name='World', greetings='Hello'):
+ print "%s, %s!" % (greetings, name)
+
+This function can be called in one of the following ways. It is important to
+note that no restriction is imposed in the order in which *Keyword arguments*
+can be specified. Also note, that we have combined *Keyword arguments* with
+*Default arguments* in this example, however it is not necessary::
+
+ wish(name='Guido', greetings='Hey')
+ wish(greetings='Hey', name='Guido')
+
+Calling functions by specifying arguments in the order of parameters specified
+in the function definition is called as *Positional arguments*, as opposed to
+*Keyword arguments*. It is possible to use both *Positional arguments* and
+*Keyword arguments* in a single function call. But Python doesn't allow us to
+bungle up both of them. The arguments to the function, in the call, must always
+start with *Positional arguments* which is in turn followed by *Keyword
+arguments*::
+
+ def my_func(x, y, z, u, v, w):
+ # initialize variables.
+ ...
+ # do some stuff
+ ...
+ # return the value
+
+It is valid to call the above functions in the following ways::
+
+ my_func(10, 20, 30, u=1.0, v=2.0, w=3.0)
+ my_func(10, 20, 30, 1.0, 2.0, w=3.0)
+ my_func(10, 20, z=30, u=1.0, v=2.0, w=3.0)
+ my_func(x=10, y=20, z=30, u=1.0, v=2.0, w=3.0)
+
+Following lists some of the invalid calls::
+
+ my_func(10, 20, z=30, 1.0, 2.0, 3.0)
+ my_func(x=10, 20, z=30, 1.0, 2.0, 3.0)
+ my_func(x=10, y=20, z=30, u=1.0, v=2.0, 3.0)
+
+Parameter Packing and Unpacking
+-------------------------------
+
+The positional arguments passed to a function can be collected in a tuple
+parameter and keyword arguments can be collected in a dictionary. Since keyword
+arguments must always be the last set of arguments passed to a function, the
+keyword dictionary parameter must be the last parameter. The function definition
+must include a list explicit parameters, followed by tuple paramter collecting
+parameter, whose name is preceded by a *****, for collecting positional
+parameters, in turn followed by the dictionary collecting parameter, whose name
+is preceded by a ****** ::
+
+ def print_report(title, *args, **name):
+ """Structure of *args*
+ (age, email-id)
+ Structure of *name*
+ {
+ 'first': First Name
+ 'middle': Middle Name
+ 'last': Last Name
+ }
+ """
+
+ print "Title: %s" % (title)
+ print "Full name: %(first)s %(middle)s %(last)s" % name
+ print "Age: %d\nEmail-ID: %s" % args
+
+The above function can be called as. Note, the order of keyword parameters can
+be interchanged::
+
+ >>> print_report('Employee Report', 29, 'johny@example.com', first='Johny',
+ last='Charles', middle='Douglas')
+ Title: Employee Report
+ Full name: Johny Douglas Charles
+ Age: 29
+ Email-ID: johny@example.com
+
+The reverse of this can also be achieved by using a very identical syntax while
+calling the function. A tuple or a dictionary can be passed as arguments in
+place of a list of *Positional arguments* or *Keyword arguments* respectively
+using ***** or ****** ::
+
+ def print_report(title, age, email, first, middle, last):
+ print "Title: %s" % (title)
+ print "Full name: %s %s %s" % (first, middle, last)
+ print "Age: %d\nEmail-ID: %s" % (age, email)
+
+ >>> args = (29, 'johny@example.com')
+ >>> name = {
+ 'first': 'Johny',
+ 'middle': 'Charles',
+ 'last': 'Douglas'
+ }
+ >>> print_report('Employee Report', *args, **name)
+ Title: Employee Report
+ Full name: Johny Charles Douglas
+ Age: 29
+ Email-ID: johny@example.com
+
+Nested Functions and Scopes
+---------------------------
+
+Python allows nesting one function inside another. This style of programming
+turns out to be extremely flexible and powerful features when we use *Python
+decorators*. We will not talk about decorators is beyond the scope of this
+course. If you are interested in knowing more about *decorator programming* in
+Python you are suggested to read:
+
+| http://avinashv.net/2008/04/python-decorators-syntactic-sugar/
+| http://personalpages.tds.net/~kent37/kk/00001.html
+
+However, the following is an example for nested functions in Python::
+
+ def outer():
+ print "Outer..."
+ def inner():
+ print "Inner..."
+ print "Outer..."
+ inner()
+
+ >>> outer()
+