summaryrefslogtreecommitdiff
path: root/basic_python/func.rst
diff options
context:
space:
mode:
Diffstat (limited to 'basic_python/func.rst')
-rw-r--r--basic_python/func.rst753
1 files changed, 417 insertions, 336 deletions
diff --git a/basic_python/func.rst b/basic_python/func.rst
index df3452a..bd9074b 100644
--- a/basic_python/func.rst
+++ b/basic_python/func.rst
@@ -1,339 +1,420 @@
-Functional Approach
-===================
+Functions
+=========
-*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
+We are now going to learn about functions in Python --- how to define
+them, passing arguments to them, docstrings, and return values.
+
+While writing code, fewer lines of code is a good thing, since it reduces the
+scope of error. Also, we would like to reduce duplication of code and
+abstract out pieces of code, wherever possible. Functions allow us to do all
+of this.
+
+Now let us at functions in a greater detail,
+
+Consider a mathematical function ``f(x) = x^2``. Here ``x`` is a variable and
+with different values of ``x`` the value of function will change. When ``x``
+is one f(1) will return the value 1 and f(2) will return us the value 4. Let
+us now see how to define the function f(x) in Python.
+
+::
+
+ def f(x):
+ return x*x
+
+Well that defined the function, so before learning what we did let us
+see if it returns the expected values, try,
+
+::
+
+ f(1)
+ f(2)
+
+Yes, it returned 1 and 4 respectively. And now let us see what we did. We
+wrote two lines: The first line ``def f(x):`` defines the name of the
+function and specifies the parameters to the function. The second line
+specifies what the function is supposed to return. ``def`` is a keyword and
+``f`` is the name of the function and ``x`` the parameter of the function.
+
+You can also have functions without any arguments.
+
+Let us define a new function called ``greet`` which will print ``Hello
+World``.
+
+::
+
+ def greet():
+ print "Hello World!"
+
+now try calling the function,
+
+::
+
+ greet()
+
+Well that is a function which takes no arguments. Also note that it is not
+mandatory for a function to return values. The function ``greet`` isn't
+taking any argument. Also, it is not returning any value explicitly. But for
+such functions, Python returns a ``None`` object by default
+
+Now let us see how to write functions with more than one argument.
+
+::
+
+ def avg(a, b):
+ return (a + b)/2
+
+If we want a function to accept more arguments, we just list them separated
+with a comma between the parenthesis after the function's name in the ``def``
+line.
+
+It is always a good practice to document the code that we write, and
+for a function we define we should write an abstract of what the
+function does, and that is called a docstring.
+
+Let us modify the function ``avg`` and add docstring to it.
+
+::
+
+ def avg(a,b):
+ """ avg takes two numbers as input, and
+ returns their average"""
+
+ return (a+b)/2
+
+Note that docstrings are entered in the line immediately after the function
+definition and put as a triple quoted string.
+
+Now we try this in the IPython interpreter,
+
+::
+
+ avg?
+
+it displays the docstring as we gave it. Thus docstring has practical utility
+also, and is not just a good "practice".
+
+Try to do this,
+
+::
+
+ greet?
+
+It doesn't have a docstring associated with it. Also we cannot infer anything
+from the function name, and thus we are forced to read the code to understand
+about the function.
+
+Let's now write a function named ``circle`` which returns the area and
+perimeter of a circle given radius ``r``.
+
+The function needs to return two values instead of just one which was being
+returned until now.
+
+::
+
+ def circle(r):
+ """returns area and perimeter of a circle given radius r"""
+ pi = 3.14
+ area = pi * r * r
+ perimeter = 2 * pi * r
+ return area, perimeter
+
+Similarly, you could have a function returning three or four or any number of
+values. A Python function can return any number of values and there is not
+restriction on it.
+
+Let us call the function ``circle`` as,
+
+::
+
+ a, p = circle(6)
+ print a
+ print p
+
+Let us now do a little code reading, as opposed to writing.
+
+What does the function ``what`` do?
+
+::
+
+ def what( n ):
+ if n < 0: n = -n
+ while n > 0:
+ if n % 2 == 1:
+ return False
+ n /= 10
+ return True
+
+The function returns ``True`` if all the digits of the number ``n`` are even,
+otherwise it returns ``False``.
+
+::
+
+ def even_digits( n ):
+ """returns True if all the digits in the number n are even,
+ returns False if all the digits in the number n are not even"""
+ if n < 0: n = -n
+ while n > 0:
+ if n % 2 == 1:
+ return False
+ n /= 10
+ return True
+
+
+Now one more code reading exercise,
+
+What does this function ``what`` do?
+
+::
+
+ def what( n ):
+ i = 1
+ while i * i < n:
+ i += 1
+ return i * i == n, i
+
+The function returns ``True`` and the square root of ``n`` if n is a perfect
+square, otherwise it returns ``False`` and the square root of the next
+perfect square.
+
+::
+
+ def is_perfect_square( n ):
+ """returns True and square root of n, if n is a perfect square,
+ otherwise returns False and the square root of the
+ next perfect square"""
+ i = 1
+ while i * i < n:
+ i += 1
+ return i * i == n, i
+
+Default & Keyword Arguments
---------------------------
-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()
-
-map, reduce and filter functions
---------------------------------
-
-Python provides several built-in functions for convenience. The **map()**,
-**reduce()** and **filter()** functions prove to be very useful with sequences like
-*Lists*.
-
-The **map** (*function*, *sequence*) function takes two arguments: *function*
-and a *sequence* argument. The *function* argument must be the name of the
-function which in turn takes a single argument, the individual element of the
-*sequence*. The **map** function calls *function(item)*, for each item in the
-sequence and returns a list of values, where each value is the value returned
-by each call to *function(item)*. **map()** function allows to pass more than
-one sequence. In this case, the first argument, *function* must take as many
-arguments as the number of sequences passed. This function is called with each
-corresponding element in the each of the sequences, or **None** if one of the
-sequence is exhausted::
-
- def square(x):
- return x*x
-
- >>> map(square, [1, 2, 3, 4])
- [1, 4, 9, 16]
-
- def mul(x, y):
- return x*y
-
- >>> map(mul, [1, 2, 3, 4], [6, 7, 8, 9])
-
-The **filter** (*function*, *sequence*) function takes two arguments, similar to
-the **map()** function. The **filter** function calls *function(item)*, for each
-item in the sequence and returns all the elements in the sequence for which
-*function(item)* returned True::
-
- def even(x):
- if x % 2:
- return True
- else:
- return False
-
- >>> filter(even, range(1, 10))
- [1, 3, 5, 7, 9]
-
-The **reduce** (*function*, *sequence*) function takes two arguments, similar to
-**map** function, however multiple sequences are not allowed. The **reduce**
-function calls *function* with first two consecutive elements in the sequence,
-obtains the result, calls *function* with the result and the subsequent element
-in the sequence and so on until the end of the list and returns the final result::
-
- def mul(x, y):
- return x*y
-
- >>> reduce(mul, [1, 2, 3, 4])
- 24
-
-List Comprehensions
-~~~~~~~~~~~~~~~~~~~
-
-List Comprehension is a convenvience utility provided by Python. It is a
-syntatic sugar to create *Lists*. Using *List Comprehensions* one can create
-*Lists* from other type of sequential data structures or other *Lists* itself.
-The syntax of *List Comprehensions* consists of a square brackets to indicate
-the result is a *List* within which we include at least one **for** clause and
-multiple **if** clauses. It will be more clear with an example::
-
- >>> num = [1, 2, 3]
- >>> sq = [x*x for x in num]
- >>> sq
- [1, 4, 9]
- >>> all_num = [1, 2, 3, 4, 5, 6, 7, 8, 9]
- >>> even = [x for x in all_num if x%2 == 0]
-
-The syntax used here is very clear from the way it is written. It can be
-translated into english as, "for each element x in the list all_num,
-if remainder of x divided by 2 is 0, add x to the list."
+Let us now look at specifying default arguments to functions when defining
+them and calling functions using keyword arguments.
+
+Let's use the ``round`` function as an example to understand what a default
+value of an argument means. Let's type the following expressions in the
+terminal.
+
+::
+
+ round(2.484)
+
+ round(2.484, 2)
+
+Both the first expression and the second are calls to the ``round`` function,
+but the first calls it with only one argument and the second calls it with
+two arguments. By observing the output, we can guess that the first one is
+equivalent to call with the second argument being 0. 0 is the default value
+of the argument.
+
+::
+
+ s.split() # split on spaces.
+ s.split(';') # split on ';'
+
+ range(10) # returns a list with numbers from 0 to 9
+ range(1, 10) # returns a list with numbers from 1 to 9
+ range(1, 10, 2) # returns a list with odd numbers from 1 to 9
+
+Let's now define a simple function that uses default arguments. We define a
+simple function that prints a welcome message to a person, given a greeting
+and his/her name.
+
+::
+
+ def welcome(greet, name="World"):
+ print greet, name
+
+Let us first call the function with two arguments, one for ``greet`` and
+other for ``name``.
+
+::
+
+ welcome("Hi", "Guido")
+
+We get the expected welcome message, "Hi Guido".
+
+Now let us call the function with just one argument "Hello".
+
+::
+
+ welcome("Hello")
+
+"Hello" is treated as the ``greet`` and we get "Hello World" as the output.
+"World" is the default value for the argument ``name``.
+
+If we redefined the function ``welcome``, by interchanging it's arguments and
+placed the ``name`` argument with it's default value of "World" before the
+``greet`` argument, what happens?
+
+::
+
+ def welcome(name="World", greet):
+ print greet, name
+
+We get an error that reads ``SyntaxError: non-default argument follows
+default argument``. When defining a function all the argument with default
+values should come at the end.
+
+Let us now learn what keyword arguments or named arguments are. We shall
+refer to them as keyword arguments, henceforth.
+
+When you are calling functions in Python, you don't need to remember the
+order in which to pass the arguments. Instead, you can use the name of the
+argument to pass it a value. Let us understand this using the ``welcome``
+function that we have been using all along. Let us call it in different ways
+and observe the output to see how keyword arguments work.
+
+::
+
+ welcome()
+ welcome("Hello", "James")
+
+ welcome("Hi", name="Guido")
+
+When no keyword is specified, the arguments are allotted based on their
+position. So, "Hi" is the value of the argument ``greet`` and name is passed
+the value "Guido".
+
+::
+
+ welcome(name="Guido", greet="Hey! ")
+
+When keyword arguments are used, the arguments can be called in any order.
+
+::
+
+ welcome(name="Guido", "Hey")
+
+This call returns an error that reads, ``non keyword arg after keyword arg``.
+Python expects all the keyword to be present towards the end.
+
+That brings us to the end of what we wanted to learn about ``keyword``
+arguments.
+
+Before defining a function of your own, make sure that you check the standard
+library, for a similar function. Python is popularly called a "Batteries
+included" language, for the huge library that comes along with it. Refer
+`here <http://docs.python.org/library/functions.html>`_.
+
+Variable scope
+--------------
+
+Before we end the discussion on functions, a short note on the scope of
+variables in Python is in order.
+
+Arguments passed to a function are local. They are not available outside of
+the function.
+
+::
+
+ def change(q):
+ q = 10
+ print q
+
+ change(1)
+ print q
+
+The variables used inside a function definition are considered to be "local"
+variables and their existence is restricted to within the function. Global
+variables are those variables, which are accessible from anywhere within a
+Python program.
+
+Variables that are assigned to within a function, are treated as local
+variables by default.
+
+::
+
+ n = 5
+
+ def change():
+ n = 10
+ print n
+
+ change()
+ print n
+
+As you can see, the value of n hasn't changed after the function ``change``
+was called.
+
+To assign to global variables (or variables which can be accessed from
+outside the function), we need to use the global statement. We could redefine
+the change function as shown below.
+
+::
+
+ def change():
+ global n
+ n = 10
+ print n
+
+ change()
+ print n
+
+There is a subtle difference in the behavior when we assign not directly to a
+variable, but a list element or a list slice etc. In this case, Python looks
+up for the name, from the innermost scope (the function), outwards, until it
+finds the name.
+
+For example
+
+::
+
+ name = ['Mr.', 'Steve', 'Gosling']
+
+ def change_name():
+ name[0] = 'Dr.'
+
+ change_name()
+ print name
+
+As, you can see, even though there was no variable ``name`` within the scope
+of the function ``change_name``, calling it has changed the list ``name``.
+
+Also, let us tweak the examples above to learn about the way values are
+passed to functions.
+
+::
+
+ n = 5
+
+ def change(n):
+ n = 10
+ print "n = %s, inside change" %n
+
+ change(n)
+ print n
+
+::
+
+ name = ['Mr.', 'Steve', 'Gosling']
+
+ def change_name(n):
+ n[0] = 'Dr.'
+ print "n = %s, inside change_name" %n
+
+ change_name(n)
+ print name
+
+
+Notice that the value of ``n`` does not get changed in the first case,
+because numbers are immutable datatypes and they cannot be modified. In the
+second case when a list was passed to the function ``change_name``, it is
+changed because a list is mutable and it's first element is chaned by the
+function.
+
+That brings us to the end of this section on functions. We have learnt how to
+define functions, use them with default values and keyword arguments. We have
+also looked briefly at variables and their scopes.
+
+..
+ Local Variables:
+ mode: rst
+ indent-tabs-mode: nil
+ sentence-end-double-space: nil
+ fill-column: 77
+ End:
+
+