Functions

Functions are first-class citizens in Python. You can assign them to variables, use them as arguments to other functions, and return them from functions.

In Python, those parentheses mean call this function. With no parentheses, Python just treats the function like any other object.

You can use functions as elements of lists, tuples, sets, and dictionaries. Functions are immutable, so you can also use them as dictionary keys.

A false value doesn’t necessarily need to explicitly be False. For example, these are all considered False:

boolean          False
 null            None
 zero integer    0
 zero float      0.0
 empty string    ''
 empty list      []
 empty tuple     ()
 empty dict      {}
 empty set set   ()
None in function

 

This seems like a subtle distinction, but it’s important in Python. You’ll need None to distinguish a missing value from an empty value. Remember that zero-valued integers or floats, empty strings (”), lists ([]), tuples ((,)), dictionaries ({}), and sets(set()) are all False, but are not equal to None.

>>> def is_none(thing):
... if thing is None:
... print("It's None")
... elif thing:
... print("It's True")
... else:
... print("It's False")
>>> is_none(None)
It's None
>>> is_none(True)
It's True
>>> is_none(False)
It's False
>>> is_none(0)
It's False
>>> is_none(0.0)
It's False
>>> is_none(())
It's False
>>> is_none([])
It's False
>>> is_none({})
It's False
>>> is_none(set())
It's False
positional arguments

 

Values of the positional arguments are copied to their corresponding parameters in order.

E.g.

>>> def menu(wine, entree, dessert):
... return {'wine': wine, 'entree': entree, 'dessert': dessert}
...
>>> menu('chardonnay', 'chicken', 'cake')
{'dessert': 'cake', 'wine': 'chardonnay', 'entree': 'chicken'}


A downside of positional arguments is that you need to remember the meaning of each position.

Keyword Arguments

To avoid positional argument confusion, you can specify arguments by the names of their corresponding parameters, even in a different order from their definition in the function:

>>> menu(entree='beef', dessert='bagel', wine='bordeaux')
 {'dessert': 'bagel', 'wine': 'bordeaux', 'entree': 'beef'}

If you call a function with both positional and keyword arguments, the positional arguments need to come first.

>>> menu('frontenac', dessert='flan', entree='fish')
 {'entree': 'fish', 'dessert': 'flan', 'wine': 'frontenac'}
Specify Default Parameter Values

You can specify default values for parameters. The default is used if the caller does not provide a corresponding argument.

>>> def menu(wine, entree, dessert='pudding'):
... return {'wine': wine, 'entree': entree, 'dessert': dessert}
>>> menu('chardonnay', 'chicken')
{'dessert': 'pudding', 'wine': 'chardonnay', 'entree': 'chicken'}    //note that the argument with default parameters will go to the front.
>>> menu('dunkelfelder', 'duck', 'doughnut')
{'dessert': 'doughnut', 'wine': 'dunkelfelder', 'entree': 'duck'}

Default argument values are calculated when the function is defined,not when it is run. A common error with new (and sometimes notsonew) Python programmers is to use a mutable data type such as a list or dictionary as a default argument.

The second time, result still has one item from the previous call:

>>> def buggy(arg, result=[]):
...     result.append(arg)
...     print(result)
...
>>> buggy('a')
['a']
>>> buggy('b') # expect ['b']
['a', 'b']

To fix this,

>>> def works(arg):
...     result = []
...     result.append(arg)
... return result
...
>>> works('a')
['a']
>>> works('b')
['b']

or use another argument to force a reset:

 >>> def nonbuggy(arg, result=None):
...     if result is None:
...         result = []
...     result.append(arg)
...     print(result)
...
>>> nonbuggy('a')
['a']
>>> nonbuggy('b')
['b']
Gather Positional Arguments with * ( flexible number of arguments)

Python doesn’t have pointers.
When used inside the function with a parameter, an asterisk groups a variable number of positional arguments into a tuple of parameter values. argument start with * is also called flexible number of arguments, if you don’t know how many arguments the user will pass to the function, and the function does not care about the number of the arguments( e.g. a loop will handle this), you can use *args to pass the flexible number of arguments to the function.

>> def print_args(*args):
... print('Positional argument tuple:', args)
...

Another example is adding all the arguments together:

def add_number(*args):
    total=0
    for n in args:
        total += n
        print(total)

It’s a convention, not a must to use *args, you can use such as “*possible” or any name you are comfortable with.

add_number(12, 14)
26
add_number(1, 4, 14)
19

The adding can also be done by built-in function:

def sum_args(*args):
    return sum(args)
print(sum_args(1,2,4,5))

If you call it with no arguments, you get nothing in *args:

>> print_args()
Positional argument tuple: ()

Whatever you give it will be printed as the args tuple:

>> print_args(3, 2, 1, 'wait!', 'uh...')
Positional argument tuple: (3, 2, 1, 'wait!', 'uh...')

This is useful for writing functions such as print() that accept a variable number of arguments. If your function has required positional arguments as well, *args goes at the end and grabs all the rest:

>> def print_more(required1, required2, *args):
... print('Need this one:', required1)
... print('Need this one too:', required2)
... print('All the rest:', args)
...
>>> print_more('cap', 'gloves', 'scarf', 'monocle', 'mustache wax')
Need this one: cap
Need this one too: gloves
All the rest: ('scarf', 'monocle', 'mustache wax')

or you can use * before a object.

E.g. we define a “health index calculator” based on the age of the person, the apple rate each week, and the cigarets consumed each week:

def health_calculator(age,apples_ate,cigs_smoked):

    answer=(120-age)+(apples_ate*2.1)-(cigs_smoked*2.5)

    print(answer)


frank_data=[31,5,0]

health_calculator(31, 5, 0)   # used as a reference to test if other way get the same result

health_calculator(frank_data[0], frank_data[1], frank_data[2])

health_calculator(*frank_data)

 

Gather Keyword Arguments with **

You can use two asterisks (**) to group keyword arguments into a dictionary, where the argument names are the keys, and their values are the corresponding dictionary values.

>>> def print_kwargs(**kwargs):
... print('Keyword arguments:', kwargs)
...
>>> print_kwargs(wine='merlot', entree='mutton', dessert='macaroon')
Keyword arguments: {'dessert': 'macaroon', 'wine': 'merlot', 'entree': 'mutton'}

Inside the function, kwargs is a dictionary. If you mix positional parameters with *args and **kwargs, they need to occur in that order. As with args, you don’t need to call this keyword parameter kwargs, but it’s common usage.

Docstrings

You can attach documentation to a function definition by including a string at the beginning of the function body.

>> def echo(anything):
... 'echo returns its input argument'
... return anything

Or make a long and rich formatting by using ”’ xxxx ”’

To print a function’s docstring, call the Python help(function_name) function. Pass the function’s name to get a listing of arguments along with the nicely formatted docstring.

E.g.

help(echo)
echo returns its input argument

If you want to see just the raw docstring, without the formatting:

>>> print(echo.__doc__)
echo returns its input argument

That odd-looking __doc__ is the internal name of the docstring as a variable within the function, note that there are two _ before and after the doc.

 

 

Inner Functions

You can define a function within another function:

>>> def outer(a, b):
 ...  def inner(c, d):
 ...     return c + d
 ...  return inner(a, b)
 ...
 >>>
 >>> outer(4, 7)
 11

An inner function can be useful when performing some complex task more than once within another function, to avoid loops or code duplication. For a string example, this inner function adds some text to its argument:

>>> def knights(saying):
 ...     def inner(quote):
 ...        return "We are the knights who say: '%s'" % quote
 ...     return inner(saying)
 ...
 >>> knights('Ni!')
 "We are the knights who say: 'Ni!'"
Closures

An inner function can act as a closure. This is a function that is dynamically generated by another function and can both change and remember the values of variables that were created outside the function.

>>> def knights2(saying):
        def inner2():
            return "We are the knights who say:'%s'" % saying
        return inner2


>>> knights2('frank')
 <function inner2 at 0x0000000002B11908>
>>> a=knights2('Duck')
>>> b=knights2('hasenpfeffer')

# verify the type of object a and b
>>> type(a)
 <type 'function'>
>>> type(b)
 <type 'function'>

The inner2() function knows the value of saying that was passed in and remembers it. The line return inner2 returns this specialized copy of the inner2 function (but doesn’t call it). That’s a closure: a dynamically created function that remembers where it came from.

They’re functions, but they’re also closures:

>>> a
 <function inner2 at 0x0000000002B11978>
>>> b
 <function inner2 at 0x0000000002B119E8>

If we call them, they remember the saying that was used when they were created by knights2:

>>> a()
 "We are the knights who say:'Duck'"
>>> b()
 "We are the knights who say:'hasenpfeffer'"
>>> knights2('hello')
 <function inner2 at 0x0000000002B11908>
>>> knights2('hello')()
 "We are the knights who say:'hello'"
Input function

 

The hello program of The Classic First Program always does the same thing. This is not very interesting. Programs are only going to be reused if they can act on a variety of data. One way to get data is directly from the user by input function.

First it prints the string you give as a parameter which is used to instruct the user to type right type of data , and then it waits for a line to be typed in, and returns the string of characters you typed.

hello_you.py.

person = input('Enter your name: ')
print('Hello', person)

Run the program. In the Shell you should see

Enter your name:

Follow the instruction (and press Enter). Make sure the typing cursor is in the Shell window, at the end of this line. After you type your response, you can see that the program has taken in the line you typed. That is what the built-in function input does: First it prints the string you give as a parameter (in this case 'Enter your name:'), and then it waits for a line to be typed in, and returns the string of characters you typed. In the hello_you.py program this value is assigned to the variable person, for use later.

Anonymous Functions: the lambda() Function

In Python, a lambda function is an anonymous function expressed as a single statement. You can use it instead of a normal tiny function.

To begin, we’ll define the function edit_story(). Its arguments are the following:
• words—a list of words
• func—a function to apply to each word in words

>>> def edit_story(words, func):
 ...   for word in words:
 ...       print(func(word))

Now, we need a list of words and a function to apply to each word. For the words, here’s a list of (hypothetical) sounds made by my cat if he (hypothetically) missed one of the stairs:

>> stairs = ['thud', 'meow', 'thud', 'hiss']

And for the function, this will capitalize each word and append an exclamation point, perfect for feline tabloid newspaper headlines:

>>def enliven(word): # give that prose more punch
 ...      return word.capitalize() + '!'

Mixing our ingredients:

>> edit_story(stairs, enliven)
 Thud!
 Meow!
 Thud!
 Hiss!

Finally, we get to the lambda.

The enliven() function was so brief that we could replace it with a lambda:

>>>edit_story(stairs,lambda word: word.capitalize() + '!')
Thud!
Meow!
Thud!
Hiss!

Everything between the colon and the terminating parenthesis is the definition of the function.

 

Generators

 

A generator is a Python sequence creation object. With it, you can iterate through potentially huge sequences without creating and storing the entire sequence in memory at once.

E.g. range() in earlier code examples is used to generate a series of integers.

Every time you iterate through a generator, it keeps track of where it was the last time it was called and returns the next value. This is different from a normal function, which has no memory of previous calls and always starts at its first line with the same state.

Generator function:

Generators are used to create iterators, but with a different approach. Generators are simple functions which return an iterable set of items, one at a time, in a special way.

When an iteration over a set of item starts using the for statement, the generator is run. Once the generator’s function code reaches a “yield” statement, the generator yields its execution back to the for loop, returning a new value from the set. The generator function can generate as many values (possibly infinite) as it wants, yielding each one in its turn.

Let’s write our own version of range():

>>> def my_range(first=0, last=10, step=1):
... number = first
... while number < last:
... yield number
... number += step

Here is a simple example of a generator function which returns 7 random integers:

import random

def lottery():
    # returns 6 numbers between 1 and 40
    for i in range(6):
        yield random.randint(1, 40)

    # returns a 7th number between 1 and 15
    yield random.randint(1,15)

for random_number in lottery():
    print ("And the next number is... %d!" % random_number)
Decorators

Sometimes, you want to modify an existing function without changing its source code. A common example is adding a debugging statement to see what arguments were passed in.
A decorator is a function that takes one function as input and returns another function.

We’ll dig into our bag of Python tricks and use the following:

• *args and **kwargs
• Inner functions
• Functions as arguments

The function document_it() defines a decorator that will do the following:

• Print the function’s name and the values of its arguments
• Run the function with the arguments
• Print the result
• Return the modified function for use

Here’s what the code looks like:

>>def document_it(func):
 ...     def new_function(*args, **kwargs):
 ...         print('Running function:', func.__name__)
 ...         print('Positional arguments:', args)
 ...         print('Keyword arguments:', kwargs)
 ...         result = func(*args, **kwargs)
 ...         print('Result:', result)
 ...         return result
 ...         return new_function
 

Whatever func you pass to document_it(), you get a new function that includes the extra statements that document_it() adds.  A decorator doesn’t actually have to run any code from func, but document_it() calls func part way through so that you get the results of func as well as all the extras.

So, how do you use this? You can apply the decorator manually:

>>> def add_ints(a, b):
 ...   return a + b
 ...
 >>> add_ints(3, 5)
 8
 >>> cooler_add_ints = document_it(add_ints) # manual decorator assignment
 >>> cooler_add_ints(3, 5)
 Running function: add_ints

 Positional arguments: (3, 5)
 Keyword arguments: {}
 Result: 8
 8

As an alternative to the manual decorator assignment above, just add @decorator_name before the function that you want to decorate:

>> @document_it
 ... def add_ints(a, b):
 ...     return a + b
 ...
 >>> add_ints(3, 5)
 Start function add_ints
 Positional arguments: (3, 5)
 Keyword arguments: {}
 Result: 8
 8

You can have more than one decorator for a function.

Now we add another decorator called squre_it() that squares the result:

>>> def square_it(func):
...     def new_function(*args, **kwargs):
...         result = func(*args, **kwargs)
...         return result * result
...     return new_function

The decorator that’s used closest to the function (just above the def) runs first and then the one above it. Either order gives the same end result, but you can see how the intermediate steps change:

@document_it
@square_it
def add_ints(a, b):
    return a + b

add_ints(3, 5)
print(add_ints(3,5))

Output:

Running function: new_function
Positional arguments: (3, 5)
Keyword arguments: {}
Result: 64
64

The order is 3+5=8, then 8 is passed to function square_it, 8*8 the function square_it return the result 64.  Then 64 is passed as the argument to the function document_it,  and the result get the value 64, which is printed out in the function and the function document_it also return the 64 when the function ends.

 

Let’s try reversing the decorator order:

@square_it
@document_it
def add_ints(a, b):
    return a + b
add_ints(3,5)
print(add_ints(3,5))

output

Running function: add_ints
Positional arguments: (3, 5)
Keyword arguments: {}
Result: 8
64

The order is 3+5=8, then 8 is passed to function document_it, and the variable result get the value 8, which is printed out inside the function, the function document_it also return the 8 when the function ends. Then 8 is passed  to the function square_it and been squaredthe function square_it then return the 64 when the function ends.

Note that you still have to define the document_it() first, then use @ to decorate.

Namespaces and Scope

namespaces—sections within which a particular name is unique and unrelated to the same name in other namespaces.

The main part of a program defines the global namespace; thus, the variables in that namespace are global variables.

Local namespace: variable in a function.

Python provides two functions to access the contents of your namespaces:

• locals() returns a dictionary of the contents of the local namespace.
• globals() returns a dictionary of the contents of the global namespace.

animal = 'fruitbat'
def change_local():
    animal = 'wombat' # local variable
    print('locals:', locals())

Print(animal)

change_local()
print('globals:', globals()) # reformatted a little for presentation

The output

fruitbat
locals: {'animal': 'wombat'}
globals: {'animal': 'fruitbat',
'__doc__': None,
'change_local': <function change_it at 0x1006c0170>,
'__package__': None,
'__name__': '__main__',
'__loader__': <class '_frozen_importlib.BuiltinImporter'>,
'__builtins__': <module 'builtins'>}
Handle Errors

In some languages, errors are indicated by special function return values. Python uses exceptions: code that is executed when an associated error occurs.

It’s good practice to add exception handling anywhere an exception might occur to let the user know what is happening. You might not be able to fix the problem, but at least you can note the circumstances and shut your program down gracefully.

Rather than leaving it to chance, use try to wrap your code, and except to provide the error handling:

>>> short_list = [1, 2, 3]
 >>> position = 5
 >>> try:
 ... short_list[position]
 ... except:
 ... print('Need a position between 0 and', len(short_list)-1, ' but got',
 ... position)
 ...
 Need a position between 0 and 2 but got 5

The code inside the try block is run. If there is an error, an exception is raised and the code inside the except block runs. If there are no errors, the except block is skipped.

E.g.

> short_list = [1, 2, 3]
>>> while True:
... value = input('Position [q to quit]? ')
... if value == 'q':
......break
... try:
......position = int(value)
... ...print(short_list[position])
... except IndexError as err:
......print('Bad index:', position)
... except Exception as other:
......print('Something else broke:', other)
...
Position [q to quit]? 1
2
Position [q to quit]? 0
1
Position [q to quit]? 2
3
Position [q to quit]? 3
Bad index: 3
Position [q to quit]? 2
3
Position [q to quit]? two
Something else broke: invalid literal for int() with base 10: 'two'
Position [q to quit]? q

Inputting position 3 raised an IndexError as expected. Entering two annoyed the int() function, which we handled in our second, catchall exception.

while True:

    try:

        number = int(input("what is your fav number hoss? \n"))

        print(18/number)

        break

    except ValueError:

        print("make sure and enter a nubmer:.")

Because the number is denominator, there will be an error if it is 0:

 

what is your fav number hoss? 

0

Traceback (most recent call last):

  File "/Volumes/Data/eclips/Learning/Chapter4 Code structure/errorhandle2.py", line 4, in <module>

    print(18/number)

ZeroDivisionError: division by zero

Here we need to catch the ZeroDivisionError and then add it as a condition:

while True:
    try:
        number = int(input("what is your fav number hoss? \n"))
        print(18/number)
        break
    except ValueError:
        print("make sure and enter a nubmer:.")
    except ZeroDivisionError:
        print("Don't pick zero!"

To handle all other errors you may not anticipate:

except:
    break

 

finally:

Basically means execute this no matter what happened, it can be a notification.

E.g.

Finally:
    print("loop complete")
Related topics