Python: Control Statements

This is part of my Python & Django Series which can be found here including information on how to download all the source code.

Controlling the flow of code is an essential building block of any language. Control statements include the traditional if statement all the way through to lambda expressions which have migrated from functional languages.

If, Else If, Else

The traditional if statements in Python are performed with the if, elif and else keywords.

If and elif provide a conditional test which, if evaluating to true, executes the associated code block.

An if statement must have at last one conditional test and must start with the if keyword. Subsequent conditional statements must use the elif keyword.

When a conditional statement evaluates to true, the associated code block is executed and no other conditional statement will execute.

The else keyword can be used to execute a block of code without a conditional statement, though it will only execute if no other conditional statement has evaluated to true.

The following code has two conditional statements checking if a variable is less than one or is equal to one. The provided else statement will be run for all other situations, in this case where the number is greater than 1.

Code:

def print_condition(value):
    if value < 1:
        print("@If")
    elif value == 1:
        print("@Else IF")
    else:
        print("@Else")

for x in range(3):
    print_condition(x)

Calling the if statement with values 0, 1 and 2 yields the following output.

Output:

@If
@Else IF
@Else

Loops

Loops allow iterating over a block of code multiple times. Python, as per many languages, offer many approaches to accomplishing this task.

For In Loop

Probably the most useful and simplest to write is the for in loop. This allows iterating through all elements within a collection starting at the first element and ending at the last.

Here we create a list of elements 1, 2 and 3 and iterate through each element once.

Code:

for number in [1, 2, 3]:
    print(number)

Output:

1
2
3

One thing to note is that changes to the collection during iteration will affect the number of iterations. For example if we add an element into the element during each iteration we will have an infinite loop.

The following uses a shallow copy to clone the collection before iterating, This allows us to manipulate the contents of the collection without affecting the intended iteration cycles.

Code:

letters = ['a', 'b', 'c']
for letter in letters[:]:
    letters.append(letter)

print(letters)

Output:

[‘a’, ‘b’, ‘c’, ‘a’, ‘b’, ‘c’]

Enumerate

The enumerate function allows iterating through a collection of elements along with the ordinal position of the element. As per most languages the first element within a collection is 0 and the last being the size – 1.

Code:

letters = ['a', 'b', 'c']
for index, letter in enumerate(letters[:]):
    print("{0} => {1}".format(index, letter))

Output:

0 => a
1 => b
2 => c

Range Function

The range function creates a collection of integers defined by a start integer, an ending integer and the increment.

The following creates a list of integers between 10 and 20 with an increment of 3.

Code:

for number in range(10, 20, 3):  # start, stop, step
    print(number)

Output:

10
13
16
19

The elements can be in descending order by implementing a negative increment.

Code:

for number in range(3, 0, -1):  # start, stop, step
    print(number)

Output:

3
2
1

The elements can only be integers, passing in floats to range() causes a traceback.

Code:

# This does not work!!!!
for number in range(0.1, 0.2, 0.01):  # start, stop, step
    print(number)

Break, Continue & Else

Sometimes you need more control of code execution over and beyond simply iterating through a list of elements. Python provides the continue, break and else keywords.

The continue keyword will stop the current iteration and start the execution of the next at the top of the loop.

The break keyword stops the execution of the loop immediately. Control of code is passed to the first line below the loop code.

The else keyword provides a code block which is always run immediately after the completion of the last iteration unless the loop has been terminated with the break keyword.

Code:

for number in range(10):  # 0-9
    if number == 0:
        print(number, "continue")
        continue
    print(number, "break")
    break
else:
    print("else")  #Always runs unless the loop is terminated with a break

Here we iterate through 0 to 9, the loop is continued if the number is 0 otherwise we break the loop.

Output:

0 continue
1 break

If we remove the break statement we can see the else block is run.

Code:

for number in range(10):  # 0-9
    if number == 0:
        print(number, "continue")
        continue
else:
    print("else")  #Always runs unless the loop is terminated with a break

Output:

0 continue
else

Zip Function

The zip function allows iterating through multiple collections of elements at the same time. Elements at the same ordinal position are provided as loop parameters during the iteration.

Code:

numbers = [1, 2, 3]
words = ['one', 'two', 'three']
letters = [ 'a', 'b']

for number, word, letter in zip(numbers, words, letters):
    print('{0} => {1} => {2}'.format(number, word, letter))

Output:

1 => one => a
2 => two => b

The number of iterations equals the number of elements in the smallest collection. Even though numbers and words have three elements, letters only has two so we iterate through elements at ordinal positions 0 and 1 only.

Reversed Function

The reversed function will create a new collection with the elements in a reversed order.

Code:

for i in reversed(range(1, 10, 2)):
    print(i)

Output:

9
7
5
3
1

Sorted Function

The sorted function will create a new collection in a sorted order. By default numbers will be in ascending order and strings will be in alphabetical order.

Code:

for f in sorted((5, 2, 3, 7, 6)):
    print(f)

We can reverse the sorting by passing in reverse=True. This is not the same as the reversed function. Here the collection is sorted but in reverse order. The reversed function reverses the order of the elements without sorting.

Output:

2
3
5
6
7

for f in sorted((5, 2, 3, 7, 6), reverse=True):
    print(f)

Output:

7
6
5
3
2

See the section on lambdas for an explanation on how to provide a custom sort order.

Key Value Pair Iteration

Dictionaries can be have their elements iterated over with inclusion of the key.

Here we create a dictionary with the elements 1 and 2 which have keys ‘one’ and ‘two’ respectively.

Iteration is made by calling items().

Code:

for k, v in {'one': 1, 'two': 2}.items():
    print(k, v)

Output:

two 2
one 1

Functions

Functions, along with classes, allow us to group blocks of code together into a reusable and callable entity.

All code which follows the function definition and is indented is considered within the scope of the function. The function can then be called by the name, parenthesis and any arguments or parameters as required or expected.

Code:

def no_argument_method():
    print("NoArgument")


def argument_method(a):
    print("Argument({0})".format(a))

Calling the methods in the following order will yield the following output.

Code:

no_argument_method()
argument_method("Bang!!!")

Output:

NoArgument
Argument(Bang!!!)

Optional Parameters

Python allows the provision of a default value for a method argument, thus allowing it to be optional.

Code:

def optional_argument_method(a="A Default Value"):
    print("OptionalArgument({0})".format(a))

Code:

optional_argument_method()
optional_argument_method("Bang!!!")

Output:

OptionalArgument(A Default Value)
OptionalArgument(Bang!!!)

Interestingly the value of the optional parameter is only evaluated once upon the compiling of the function and not during each function call. Take the following code.

x = 5

def optional_argument_as_a_variable(arg=x):
    global x
    print("x = {0}, arg = {1}".format(x, arg))

x = 6
optional_argument_as_a_variable()

The global x is assigned as the default value only once to the argument named arg. Changes to x are not reflected in arg. Inside the method, arg will always be 5 if no value is provided by the call to the function. The global x imported in to the function will always be the latest value of x.

The code outputs the following.

Output:

x = 6, arg = 5

For mutable objects the same object instance is always passed in. If we have a parameter defaulted to a list, the same list will be presented each function call. Changes to the list will be reflected between method calls.

Code:

def optional_argument_as_a_new_list(to_append, new_list=[]):
    new_list.append(to_append)
    return new_list

for x in range(4):
    print(optional_argument_as_a_new_list(x))

Output:

[0]
[0, 1]
[0, 1, 2]
[0, 1, 2, 3]

For immutable objects, changes to the value create a new object instance before the assignment and as such do not affect subsequent calls.

Code:

def increment_optional_argument(a=1):
    a += 1
    return a


for x in range(3):
    print(increment_optional_argument())

Output:

2
2
2

Named Parameters

When a function has defaulted parameters we can selectively pass in values by referencing the parameter with the name. All mandatory parameters must be accounted for.

Code:

def named_arguments(a, b=2, c=3):
    print("A: {0}, B: {1}, C: {2}".format(a, b, c))


named_arguments(2, c=1)

Here we pass in a value of 2 for parameter a, we allow the argument b to take on the default value of 2 while we pass in a value of 1 to argument c.

Output:

A: 2, B: 2, C: 1

Star Arguments

Python allows the collection or grouping of arguments passed in regardless if they were defined in the method definition.

* arguments collect all remaining parameters and places them in the argument as a tuple.

** arguments collect all named arguments and places them within a dictionary.

The following example has three parameters, a normal parameter called name, a * parameter called children and a ** parameter called characteristics.

Code:

def star_and_star_star_method(name, *children, **characteristics):
    print("Name:", name, type(name))
    print("Children:", children, (type(children)))
    print("Characteristics:", characteristics, type(characteristics))

star_and_star_star_method("Luke", "Jim,", "Bob", hair="Fair", height="Tall")

Calling the method would yield the following output.

Output:

Name: Luke
Children: (‘Jim,’, ‘Bob’)
Characteristics: {‘height’: ‘Tall’, ‘hair’: ‘Fair’}

If we had characteristics data already contained in a dictionary we could use Python’s unpacking functionality, as follows, which would yield the same output.

Code:

star_and_star_star_method("Luke", "Jim,", "Bob", **{"hair": "Fair", "height": "Tall"})

Function Documentation

We can add API documentation onto functions such as the following.

Code:

def my_function(a, *b, **c):
    """
    This does not really do anything,
    but if it did it would be the best function in the world!
    :param a: a is the
    :param b: b to the b
    :param c: c from the run dm
    :return: Not much
    """
    print(a, b, c)

We can then print this information out by either the help function or the __doc__ method of the function such as.

Code:

print(my_function.__doc__)
help(my_function)

Which will report something similar to:

Output:

This does not really do anything,
but if it did it would be the best function in the world!
:param a: a is the
:param b: b to the b
:param c: c from the run dm
:return: Not much

Lambda Expressions

Lambda expressions allow passing around, at run time, segments or blocks of code to be run. At first this might seem a little suicidal, but used wisely and sparingly this can reduce the amount of code required to be written in certain situations.

Imagine how many dedicated functions exist for filtering and sorting etc which float about the place.

The syntax is:

lambda param1, paramx-1 … paramx :

A simple lambda function which squares an integer passed in would be as follows.

Code:

return lambda x: x**2

We can return a lambda from a function into a variable which can then be passed around and called when required.

Code:

 def Square():
    return lambda x: x**2

We can grab a handle of the function and print it by:

Code:

# F is now a function
f = Square()
print(f)

Output:

<function Square.. at 0xb72743d4\>

We can call the function by:

Code:

# Call as if the function is x.
for x in range(5):
    print("The square of {0} is {1}".format(x, f(x)))

The output would be:

Output:

The square of 0 is 0
The square of 1 is 1
The square of 2 is 4
The square of 3 is 9
The square of 4 is 16

Lambdas can also be passed into any function which takes a function as an argument. The following example shows how we can provide a custom sort function for a list of tuples. The function simply returns the second element in each tuple which is then used as the order criteria. Element two is a string and as such the elements are sorted alphabetically.

Code:

named_numbers = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
print("Raw Data:", named_numbers)

named_numbers.sort(key=lambda pair: pair[1])
print("Sorted With Pair[1]:", named_numbers)

Output:

Raw Data: [(1, ‘one’), (2, ‘two’), (3, ‘three’), (4, ‘four’)]
Sorted With Pair[1]: [(4, ‘four’), (1, ‘one’), (3, ‘three’), (2, ‘two’)]

We can use the map function to convert a collection into another while providing a mapping function between the two. We can use a lambda expression as the mapping function.

Here we create a collection of elements 0 to 9 and then map them into a collection using a lambda which squares the element.

Code:

print(list(map(lambda x: x ** 2, range(10))))

Output;

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Creating Iterators

Coming back to the topic of iteration, Python allows us to iterate through any object as if it is a collection as long as it implements the method __iter__.

The ___iter__ method should return any object which responds to the method __next__.

The ___next__ method should simply return the next element in the collection.

In the following example we create a simply class which after taking a start and stop values provides an iterator between the two values incrementing by one each time.

Code:

class MyRangeClass:

    def __init__(self, start, stop):
        self.start = start
        self.index = start - 1
        self.stop = stop

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == self.stop:
            raise StopIteration
        self.index += 1
        return self.index

for x in MyRangeClass(1, 3):
    print(x)

Output:

1
2
3

The Yield Command

Keeping on the subject of custom iterators, Python also provides the yield command which allows us to iterate a method.

This special keyword pauses a function execution and returns the value associated with the yield keyword. The function is then resumed on the very next line upon the next function call. When the execution of the function terminates via the last line of code in the function, the iteration stops.

Code:

def count_down(count):
    while count > 0:
        yield count
        count -= 1

for x in count_down(3):
    print(x)

Output:

3
2
1

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s