Thursday, June 25, 2015

Python Crash Course - part two



Lecture 1

"""
1. Functions declaration

- Basic function definition

def name(parameter1, parameter2, . . .):
''' Function documentation (optional) '''
    body
"""

# STYLE: It’s a standard practice for multi-line documentation strings to give a synopsis
# of the function in the first line (brief), follow this with a blank second line, and end
# with the rest of the information.

# You can obtain documentation string with: fact.__doc__
def fact(n):
    """Return the factorial of the given number."""
    r = 1
    while n > 0:
        r = r*n
        n = n - 1
    return r

'''
A function that doesn’t return a value is called a procedure. All Python procedures are
functions; if no explicit return is executed in the procedure body, then the special
Python value None is returned.
'''

# Assign the result
x = fact(4)

'''
2. Function parameter options

The simplest way to pass parameters to a function in Python is by position. In the first
line of the function, you specify definition variable names for each parameter; when the
function is called, the parameters used in the calling code are matched to the function’s
parameter variables based on their order.
'''

# This method requires that the number of parameters used by the calling code exactly
# match the number of parameters in the function definition, or a TypeError exception will
# be raised:

def power(x, y):
    r = 1
    while y > 0:
        r = r*x
        y = y - 1
    return r

'''
2.1 Default values

Function parameters can have default values, which you declare by assigning a default
value in the first line of the function definition, like so:

def fun(arg1, arg2=default2, arg3=default3, . . .)

Any number of parameters can be given default values.

NOTE: Parameters with default values must be defined as the _last_ parameters in the
parameter list.
'''

# STYLE: See there is not space in default parameters assignment
def power(x, y=2):
    r = 1
    while y > 0:
        r = r * x
        y = y - 1
    return r

'''
2.2 Passing arguments by parameter name

This type of argument passing is called key- word passing.
'''

power(y=2, x=3)

# Keyword passing, in combination with the default argument capability of Python
# functions, can be highly useful when you’re defining functions with large numbers of
# possible arguments, most of which have common defaults.

# Suppose you have:

# def list_file_info(size=False, create_date=False, mod_date=False, ...):
#     ...get file names...
#     if size:
#         # code to get file sizes goes here
#         if create_date:
#             # code to get create dates goes here
#         ...
#     ...
#     ...
#     return file_info_structure

# Hey do not worry - you do not need to remember all argument positions
#
# file_info = list_file_info(size=True, mod_date=True)

'''
2.3 Variable numbers of arguments

- One way handles the relatively familiar case where you wish to collect an unknown number
of arguments at the end of the argument list into a list.

- Also you can collect an arbitrary number of keyword-passed arguments, which have no
correspondingly named parameter in the function parameter list, into a dictionary.
'''

# Prefixing the _final_ parameter name of the function with a * causes all excess non-
# keyword arguments in a call of a function to be collected together and assigned as a
# tuple to the given parameter.

def maximum(*numbers):
    if len(numbers) == 0:
        return None
    else:
        maxnum = numbers[0]
    for n in numbers[1:]:
        if n > maxnum:
            maxnum = n
    return maxnum

# Here is how we call it:
maximum(2, 3, 4, 5)

# If the _final_ parameter in the parameter list is prefixed with **, it will collect all
# excess keyword-passed arguments into a dictionary. The index for each entry in the
# dictionary will be the keyword (parameter name) for the excess argument.

def example_fun(x, y, **other):
    print("x: {0}, y: {1}, keys in 'other': {2}".format(x, y, list(other.keys())))
    other_total = 0
    for k in other.keys():
        other_total = other_total + other[k]
        print("The total of values in 'other' is {0}".format(other_total))


# Here is an example call:
example_fun(2, y="1", foo=3, bar=4)

'''
3. Mutable objects as arguments

REMEMBER: Arguments are passed in by object reference!

- Immutable objects (such as tuples, strings, and numbers)
What is done with a parameter has no effect outside the function.

- Mutable objects (for example, a list, dictionary, or class instance),
Any change made to the object will change what the argument is referencing outside the
function.
'''

# Example is always better:
def f(n, list1, list2):
    list1.append("add new value")
    list2 = ["a", "completely" , "new", "value"]
    n = n + 1

x = 5         # for n
y = [1, 2]    # for list1
z = [3, 4]    # for list2. Be careful!!

f(x, y, z)
print x, y, z

'''
At the begining:
z                list2
\                 /
 \               /
  \             /
   +-----------+
   |  [3, 4]   |
   +-----------+

'''

'''
4. Local and global variables
'''

# REMEMBER: Functions define scope!

# Any variables in the parameter list of a function, and any variables created within a
# function by an assignment (like r = 1 in fact), are local to the function.

# You can explicitly make a variable global by declaring it so before the variable is
# used, using the _global_ statement.

def fun():
    global a # use top level
    a = 1
    b = 2

# Let's test it:

a = "one"
b = "two"

fun()
print a
print b


''''
5. Assigning functions to variables

Functions are first class objects!
'''


# Functions can be assigned, like other Python objects, to variables, as shown in the fol-
# lowing example:

def f_to_kelvin(degrees_f):
    return 273.15 + (degrees_f - 32) * 5 / 9

def c_to_kelvin(degrees_c):
    return 273.15 + degrees_c


abs_temperature = f_to_kelvin
abs_temperature(32)

abs_temperature = c_to_kelvin
abs_temperature(0)

# You can place them in lists, tuples, or dictionaries:
t = {'FtoK': f_to_kelvin, 'CtoK': c_to_kelvin}
t['FtoK'](32)

'''
6. lambda expressions (aka mini functions)

Taken directly from LISP!
lambda expressions are anonymous little functions that you can quickly define inline.

lambda argument1, argument2,... argumentN :expression using arguments

Why?
Often, a small function needs to be passed to another function.
'''

# Explain with examples:
import math
def square_root(x): return math.sqrt(x)

# with lambda (one parameter)
square_root = lambda x: math.sqrt(x)

# direct call
(lambda x: math.sqrt(x))(4)

# See more examples in next lecture be patient...

'''
7. Generator(or coroutine) functions

Why?
Generators functions allow you to declare a function that behaves like an iterator,
i.e. it can be used in a loop.

'''

# REMEMBER: Generators are memory efficient! Laziness helps for that :)

# Compare thies two implementations

def firstn(n):
    num, nums = 0, []
    while num < n:
        nums.append(num)
        num += 1
    return nums

sum_of_first_n = sum(firstn(1000000))

def firstn(n):
    num = 0
    while num < n:
        yield num
        num += 1

sum_of_first_n = sum(firstn(1000000))

# How could you define function that return all nonnegative integers?

def all_naturals():
    x = 0
    while True:
        yield x
        x += 1

33 in all_naturals()

'''
8. Functions in functions - dynamically creating a function (aka lexical closures)

'''

# LEGB Rule - order matters

# L. Local. Names assigned in any way within a function (def or lambda)), and not
#    declared global in that function.

# E. Enclosing function locals. Name in the local
#    scope of any and all enclosing functions (def or lambda), form inner to outer.

# G. Global (module). Names assigned at the top-level of a module file, or declared global
#    in a def within the file.

# B. Built-in (Python). Names preassigned in the built-in names
#    module: Python "kernel"

# Do you remember that functions creates scope?

def make_adder(n=1):
    def action(x):    # inner functions could use all enclosing variables
        return x + n
    return action

# using lambda
def make_adder(n=1):
    return lambda x: x + n

one_adder = maker_adder() #=> one_adder = lambda x: x + 1
one_adder(1)

# nested lambdas
one_adder = (lambda n: lambda x: x + n)(1)

# NOTE: You can't reference inner function directly

# With nested functions (closures) you create a function with state (initial value)

two_adder = make_adder(2)
two_adder(1)

'''
9. Decorators

A decorator is just a callable that takes a function as an argument and returns a
replacement function.
'''

def logger(func):
    def inner(*args, **kwargs):        # all kind of parameters
        print "Arguments were: %s, %s" % (args, kwargs)
        return func(*args, **kwargs)   # call 'real' function!
    return inner

# Here is how to use it:
@logger
def foo(x, y=1):
    return x * y # logger(foo(30))

foo(30)

'''
10. Multi-value return
'''

def divide(x, y):
    quotient = x / y
    remainder = x % y
    return quotient, remainder

all = divide(22, 7)
all, _ = divide(22, 7)
q, r = divide(22, 7)

'''
11. __ and __{some}__ functions

Naming convention:

_  for protected

__ for private

'''

No comments:

algorithms (1) cpp (3) cv (1) daily (4) emacs (2) freebsd (4) java (3) javascript (1) JSON (1) linux (2) Lisp (7) misc (8) programming (16) Python (4) SICP (1) source control (4) sql (1) думи (8)