basic_python/func.rst
changeset 47 e724f1ee6e51
equal deleted inserted replaced
46:b4d0089294b9 47:e724f1ee6e51
       
     1 Functional Approach
       
     2 ===================
       
     3 
       
     4 *Functions* allow us to enclose a set of statements and call the function again
       
     5 and again instead of repeating the group of statements everytime. Functions also
       
     6 allow us to isolate a piece of code from all the other code and provides the
       
     7 convenience of not polluting the global variables.
       
     8 
       
     9 *Function* in python is defined with the keyword **def** followed by the name
       
    10 of the function, in turn followed by a pair of parenthesis which encloses the
       
    11 list of parameters to the function. The definition line ends with a ':'. The
       
    12 definition line is followed by the body of the function intended by one block.
       
    13 The *Function* must return a value::
       
    14 
       
    15   def factorial(n):
       
    16     fact = 1
       
    17     for i in range(2, n):
       
    18       fact *= i
       
    19 
       
    20     return fact
       
    21 
       
    22 The code snippet above defines a function with the name factorial, takes the
       
    23 number for which the factorial must be computed, computes the factorial and
       
    24 returns the value.
       
    25 
       
    26 A *Function* once defined can be used or called anywhere else in the program. We
       
    27 call a fucntion with its name followed by a pair of parenthesis which encloses
       
    28 the arguments to the function.
       
    29 
       
    30 The value that function returns can be assigned to a variable. Let's call the
       
    31 above function and store the factorial in a variable::
       
    32 
       
    33   fact5 = factorial(5)
       
    34 
       
    35 The value of fact5 will now be 120, which is the factorial of 5. Note that we
       
    36 passed 5 as the argument to the function.
       
    37 
       
    38 It may be necessary to document what the function does, for each of the function
       
    39 to help the person who reads our code to understand it better. In order to do
       
    40 this Python allows the first line of the function body to be a string. This
       
    41 string is called as *Documentation String* or *docstring*. *docstrings* prove
       
    42 to be very handy since there are number of tools which can pull out all the
       
    43 docstrings from Python functions and generate the documentation automatically
       
    44 from it. *docstrings* for functions can be written as follows::
       
    45 
       
    46   def factorial(n):
       
    47     'Returns the factorial for the number n.'
       
    48     fact = 1
       
    49     for i in range(2, n):
       
    50       fact *= i
       
    51 
       
    52     return fact
       
    53 
       
    54 An important point to note at this point is that, a function can return any
       
    55 Python value or a Python object, which also includes a *Tuple*. A *Tuple* is
       
    56 just a collection of values and those values themselves can be of any other
       
    57 valid Python datatypes, including *Lists*, *Tuples*, *Dictionaries* among other
       
    58 things. So effectively, if a function can return a tuple, it can return any
       
    59 number of values through a tuple
       
    60 
       
    61 Let us write a small function to swap two values::
       
    62 
       
    63   def swap(a, b):
       
    64     return b, a
       
    65 
       
    66   c, d = swap(a, b)
       
    67 
       
    68 Function scope
       
    69 ---------------
       
    70 The variables used inside the function are confined to the function's scope
       
    71 and doesn't pollute the variables of the same name outside the scope of the
       
    72 function. Also the arguments passed to the function are passed by-value if
       
    73 it is of basic Python data type::
       
    74 
       
    75   def cant_change(n):
       
    76     n = 10
       
    77 
       
    78   n = 5
       
    79   cant_change(n)
       
    80 
       
    81 Upon running this code, what do you think would have happened to value of n
       
    82 which was assigned 5 before the function call? If you have already tried out
       
    83 that snippet on the interpreter you already know that the value of n is not
       
    84 changed. This is true of any immutable types of Python like *Numbers*, *Strings*
       
    85 and *Tuples*. But when you pass mutable objects like *Lists* and *Dictionaries*
       
    86 the values are manipulated even outside the function::
       
    87 
       
    88   >>> def can_change(n):
       
    89   ...   n[1] = James
       
    90   ...
       
    91 
       
    92   >>> name = ['Mr.', 'Steve', 'Gosling']
       
    93   >>> can_change(name)
       
    94   >>> name
       
    95   ['Mr.', 'James', 'Gosling']
       
    96 
       
    97 If nothing is returned by the function explicitly, Python takes care to return
       
    98 None when the funnction is called.
       
    99 
       
   100 Default Arguments
       
   101 -----------------
       
   102 
       
   103 There may be situations where we need to allow the functions to take the
       
   104 arguments optionally. Python allows us to define function this way by providing
       
   105 a facility called *Default Arguments*. For example, we need to write a function
       
   106 that returns a list of fibonacci numbers. Since our function cannot generate an
       
   107 infinite list of fibonacci numbers, we need to specify the number of elements
       
   108 that the fibonacci sequence must contain. Suppose, additionally, we want to the
       
   109 function to return 10 numbers in the sequence if no option is specified we can
       
   110 define the function as follows::
       
   111 
       
   112   def fib(n=10):
       
   113     fib_list = [0, 1]
       
   114     for i in range(n - 2):
       
   115       next = fib_list[-2] + fib_list[-1]
       
   116       fib_list.append(next)
       
   117     return fib_list
       
   118 
       
   119 When we call this function, we can optionally specify the value for the
       
   120 parameter n, during the call as an argument. Calling with no argument and
       
   121 argument with n=5 returns the following fibonacci sequences::
       
   122 
       
   123   fib()
       
   124   [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
       
   125   fib(5)
       
   126   [0, 1, 1, 2, 3]
       
   127 
       
   128 Keyword Arguments
       
   129 -----------------
       
   130 
       
   131 When a function takes a large number of arguments, it may be difficult to
       
   132 remember the order of the parameters in the function definition or it may
       
   133 be necessary to pass values to only certain parameters since others take
       
   134 the default value. In either of these cases, Python provides the facility
       
   135 of passing arguments by specifying the name of the parameter as defined in
       
   136 the function definition. This is known as *Keyword Arguments*. 
       
   137 
       
   138 In a function call, *Keyword arguments* can be used for each argument, in the
       
   139 following fashion::
       
   140 
       
   141   argument_name=argument_value
       
   142   Also denoted as: keyword=argument
       
   143 
       
   144   def wish(name='World', greetings='Hello'):
       
   145     print "%s, %s!" % (greetings, name)
       
   146 
       
   147 This function can be called in one of the following ways. It is important to
       
   148 note that no restriction is imposed in the order in which *Keyword arguments*
       
   149 can be specified. Also note, that we have combined *Keyword arguments* with
       
   150 *Default arguments* in this example, however it is not necessary::
       
   151 
       
   152   wish(name='Guido', greetings='Hey')
       
   153   wish(greetings='Hey', name='Guido')
       
   154 
       
   155 Calling functions by specifying arguments in the order of parameters specified
       
   156 in the function definition is called as *Positional arguments*, as opposed to
       
   157 *Keyword arguments*. It is possible to use both *Positional arguments* and 
       
   158 *Keyword arguments* in a single function call. But Python doesn't allow us to
       
   159 bungle up both of them. The arguments to the function, in the call, must always
       
   160 start with *Positional arguments* which is in turn followed by *Keyword
       
   161 arguments*::
       
   162 
       
   163   def my_func(x, y, z, u, v, w):
       
   164     # initialize variables.
       
   165     ...
       
   166     # do some stuff 
       
   167     ...
       
   168     # return the value
       
   169 
       
   170 It is valid to call the above functions in the following ways::
       
   171 
       
   172   my_func(10, 20, 30, u=1.0, v=2.0, w=3.0)
       
   173   my_func(10, 20, 30, 1.0, 2.0, w=3.0)
       
   174   my_func(10, 20, z=30, u=1.0, v=2.0, w=3.0)
       
   175   my_func(x=10, y=20, z=30, u=1.0, v=2.0, w=3.0)
       
   176 
       
   177 Following lists some of the invalid calls::
       
   178 
       
   179   my_func(10, 20, z=30, 1.0, 2.0, 3.0)
       
   180   my_func(x=10, 20, z=30, 1.0, 2.0, 3.0)
       
   181   my_func(x=10, y=20, z=30, u=1.0, v=2.0, 3.0)
       
   182 
       
   183 Parameter Packing and Unpacking
       
   184 -------------------------------
       
   185 
       
   186 The positional arguments passed to a function can be collected in a tuple
       
   187 parameter and keyword arguments can be collected in a dictionary. Since keyword
       
   188 arguments must always be the last set of arguments passed to a function, the
       
   189 keyword dictionary parameter must be the last parameter. The function definition
       
   190 must include a list explicit parameters, followed by tuple paramter collecting
       
   191 parameter, whose name is preceded by a *****, for collecting positional
       
   192 parameters, in turn followed by the dictionary collecting parameter, whose name
       
   193 is preceded by a ****** ::
       
   194 
       
   195   def print_report(title, *args, **name):
       
   196     """Structure of *args*
       
   197     (age, email-id)
       
   198     Structure of *name*
       
   199     {
       
   200         'first': First Name
       
   201         'middle': Middle Name
       
   202         'last': Last Name
       
   203     }
       
   204     """
       
   205     
       
   206     print "Title: %s" % (title)
       
   207     print "Full name: %(first)s %(middle)s %(last)s" % name
       
   208     print "Age: %d\nEmail-ID: %s" % args
       
   209 
       
   210 The above function can be called as. Note, the order of keyword parameters can
       
   211 be interchanged::
       
   212 
       
   213   >>> print_report('Employee Report', 29, 'johny@example.com', first='Johny',
       
   214                    last='Charles', middle='Douglas')
       
   215   Title: Employee Report
       
   216   Full name: Johny Douglas Charles
       
   217   Age: 29
       
   218   Email-ID: johny@example.com
       
   219 
       
   220 The reverse of this can also be achieved by using a very identical syntax while
       
   221 calling the function. A tuple or a dictionary can be passed as arguments in
       
   222 place of a list of *Positional arguments* or *Keyword arguments* respectively
       
   223 using ***** or ****** ::
       
   224 
       
   225   def print_report(title, age, email, first, middle, last):
       
   226     print "Title: %s" % (title)
       
   227     print "Full name: %s %s %s" % (first, middle, last)
       
   228     print "Age: %d\nEmail-ID: %s" % (age, email)
       
   229 
       
   230   >>> args = (29, 'johny@example.com')
       
   231   >>> name = {
       
   232           'first': 'Johny',
       
   233           'middle': 'Charles',
       
   234           'last': 'Douglas'
       
   235           }
       
   236   >>> print_report('Employee Report', *args, **name)
       
   237   Title: Employee Report
       
   238   Full name: Johny Charles Douglas
       
   239   Age: 29
       
   240   Email-ID: johny@example.com
       
   241 
       
   242 Nested Functions and Scopes
       
   243 ---------------------------
       
   244 
       
   245 Python allows nesting one function inside another. This style of programming
       
   246 turns out to be extremely flexible and powerful features when we use *Python
       
   247 decorators*. We will not talk about decorators is beyond the scope of this
       
   248 course. If you are interested in knowing more about *decorator programming* in
       
   249 Python you are suggested to read:
       
   250 
       
   251 | http://avinashv.net/2008/04/python-decorators-syntactic-sugar/
       
   252 | http://personalpages.tds.net/~kent37/kk/00001.html
       
   253 
       
   254 However, the following is an example for nested functions in Python::
       
   255 
       
   256   def outer():
       
   257     print "Outer..."
       
   258     def inner():
       
   259       print "Inner..."
       
   260     print "Outer..."
       
   261     inner()
       
   262   
       
   263   >>> outer()
       
   264 
       
   265 map, reduce and filter functions
       
   266 --------------------------------
       
   267 
       
   268 Python provides several built-in functions for convenience. The **map()**,
       
   269 **reduce()** and **filter()** functions prove to be very useful with sequences like
       
   270 *Lists*.
       
   271 
       
   272 The **map** (*function*, *sequence*) function takes two arguments: *function*
       
   273 and a *sequence* argument. The *function* argument must be the name of the
       
   274 function which in turn takes a single argument, the individual element of the
       
   275 *sequence*. The **map** function calls *function(item)*, for each item in the
       
   276 sequence and returns a list of values, where each value is the value returned
       
   277 by each call to *function(item)*. **map()** function allows to pass more than
       
   278 one sequence. In this case, the first argument, *function* must take as many
       
   279 arguments as the number of sequences passed. This function is called with each
       
   280 corresponding element in the each of the sequences, or **None** if one of the
       
   281 sequence is exhausted::
       
   282 
       
   283   def square(x):
       
   284     return x*x
       
   285   
       
   286   >>> map(square, [1, 2, 3, 4])
       
   287   [1, 4, 9, 16]
       
   288   
       
   289   def mul(x, y):
       
   290     return x*y
       
   291   
       
   292   >>> map(mul, [1, 2, 3, 4], [6, 7, 8, 9])
       
   293 
       
   294 The **filter** (*function*, *sequence*) function takes two arguments, similar to
       
   295 the **map()** function. The **filter** function calls *function(item)*, for each
       
   296 item in the sequence and returns all the elements in the sequence for which 
       
   297 *function(item)* returned True::
       
   298 
       
   299   def even(x):
       
   300     if x % 2:
       
   301       return True
       
   302     else:
       
   303       return False
       
   304   
       
   305   >>> filter(even, range(1, 10))
       
   306   [1, 3, 5, 7, 9]
       
   307 
       
   308 The **reduce** (*function*, *sequence*) function takes two arguments, similar to
       
   309 **map** function, however multiple sequences are not allowed. The **reduce**
       
   310 function calls *function* with first two consecutive elements in the sequence,
       
   311 obtains the result, calls *function* with the result and the subsequent element
       
   312 in the sequence and so on until the end of the list and returns the final result::
       
   313 
       
   314   def mul(x, y):
       
   315     return x*y
       
   316 
       
   317   >>> reduce(mul, [1, 2, 3, 4])
       
   318   24
       
   319 
       
   320 List Comprehensions
       
   321 ~~~~~~~~~~~~~~~~~~~
       
   322 
       
   323 List Comprehension is a convenvience utility provided by Python. It is a 
       
   324 syntatic sugar to create *Lists*. Using *List Comprehensions* one can create
       
   325 *Lists* from other type of sequential data structures or other *Lists* itself.
       
   326 The syntax of *List Comprehensions* consists of a square brackets to indicate
       
   327 the result is a *List* within which we include at least one **for** clause and
       
   328 multiple **if** clauses. It will be more clear with an example::
       
   329 
       
   330   >>> num = [1, 2, 3]
       
   331   >>> sq = [x*x for x in num]
       
   332   >>> sq
       
   333   [1, 4, 9]
       
   334   >>> all_num = [1, 2, 3, 4, 5, 6, 7, 8, 9]
       
   335   >>> even = [x for x in all_num if x%2 == 0]
       
   336 
       
   337 The syntax used here is very clear from the way it is written. It can be 
       
   338 translated into english as, "for each element x in the list all_num, 
       
   339 if remainder of x divided by 2 is 0, add x to the list."