Python Code Suites: Conditionals, Loops, and Functions

Blocks of Python code are called suites; suites are delimited by tabs, not brackets or end statements.

IF Statements

As with all code suites, tabs mark what belongs to what suite. Beyond tabs, no big surprises here.

In [13]:
if True:
    print('A WINNAR IS YOU!')
if False:
    print('YOU FAILED IT.')
A WINNAR IS YOU!

Note the use of reserved words True and False (case matters in Python!)
Things that are also True:

  • Non-empty lists/tuples/dictionaries.
  • Non-null strings.
  • Declared objects, functions, etc.

It follows that these things are False:

  • Empty lists/tuples/dictionaries.
  • Null strings ('').
  • The None object.
In [14]:
if 'A word':
    print('Non-null strings!')
if [1,2,3]:
    print('Lists with elements!')
if lambda x: x**2:
    print('Declared functions!')
Non-null strings!
Lists with elements!
Declared functions!
In [15]:
if '':
    print('Null strings!')
if []:
    print('Empty strings!')
if None:
    print('None objects!')

The usual relational operators apply.

  • == (equality)
  • != (inequality)
  • <, >, <=, >= (less than, etc.)
  • and, or
In [16]:
if (3>1) and ('abcd'=='abcdefg'[0:4]):
    print('You betcha.')
You betcha.

Using tabs to delimit different suites makes code easy to read, even if you nest suites!

In [17]:
if True:
    print('Some commands before!')
    print('Many of them.')
    if True:
        print('Inside a nested suite.')
        print('Continue nested suite.')
    print('This line is in the outer "if" statement.')
print('This command ends the outer suite.')
Some commands before!
Many of them.
Inside a nested suite.
Continue nested suite.
This line is in the outer "if" statement.
This command ends the outer suite.

Finally, there's the extra conditionals.

In [18]:
if False:
    print('Not this one.')
elif False:
    print('Nor this one.')
else:
    print('This one!')
This one!

Note the absence of a "case" or "switch" statement in Python. Many options require many if-elif-else conditionals (or, preferably, a more pythonic approach to your code!)

One final thing: all code suites must have some code; you can't have empty suites. If you need an empty suite (usually for a temporary placeholder), use the pass statement.

In [19]:
# This suite does nothing.
if True:
    pass

WHILE Loops

Again, this is pretty standard. The loop continues so long as the while conditional is met.

In [20]:
i=0
while i<5:
    print('Numba {0}'.format(i))
    i+=1
Numba 0
Numba 1
Numba 2
Numba 3
Numba 4

break leaves the current loop; continue jumps immediately to the next iteration.

In [21]:
i=0
while i< 35:
    i+=1
    if i==2: continue  # If i==2, jump to the next iteration.
    if i==5: break     # If i==5, leave the loop outright.
    print(i)
1
3
4

An Interlude: Iterators

A key to understanding Python for loops is the idea of iterators.
First, life without iterators: Let's suppose we want to loop over each of the elements of this list. We could use a while loop to progress through each list index...

In [22]:
a=['a','b','c','d','e']
i=0
while i<len(a):
    print(a[i])
    i+=1
a
b
c
d
e

Or, use an iterator. Iterators have special object methods that return the next item in order from what was used to make the iterator. You can turn any sequence into an iterator using the iter() generator function. Then, we can use the intrinsic function next() to get the next item in turn from the iterator, removing the onus of keeping track of the next entry in our loop:

In [23]:
b=iter(a)
while True:
    print(next(b))
a
b
c
d
e
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-23-95b3b06e5e86> in <module>
      1 b=iter(a)
      2 while True:
----> 3     print(next(b))

StopIteration: 

What did this buy us? Not much. We didn't need to keep track of our index integer, but when we ran out of things to iterate over, our program just crashed. We need a way to catch the error and stop the loop (this is actually pretty easy, but not practical. The beauty of Python for loops is that they generate an iterator, employ the next function internally, and stop when you're out of items. It's that easy.

In [24]:
for item in a:
    print('Our item is {}.'.format(item))
Our item is a.
Our item is b.
Our item is c.
Our item is d.
Our item is e.

What makes this especially powerful is the Pythonic concept of multiple assignments.

In [25]:
dbl_list = [ [1,'a'], [2,'b'], [3, 'e'] ]
for a, b in dbl_list:
    print('#{0} is "{1}"'.format(a, b))
#1 is "a"
#2 is "b"
#3 is "e"

What happened here is that each item that our iterator returns is a single entry from our list, "dbl_list". Each entry is a sublist with two sub-elements, so we assign each sub-element to its own variable in our for loop. Our "dbl_list" is inconvenient, but there are a bunch of built-in functions that make for very, very powerful. enumerate() returns a set of index-value pairs while zip() zips multiple sequences together by like-element pairs (or more).

In [26]:
list1=['dog', 'cat', 'bobcat']
list2=['puppy','kitten', 'bobkitty']
list3=['bazooka','tank','lazer']

for i, a in enumerate(list1):
    print('Animal #{0} is {1}'.format(i, a))
    
for a, b, c in zip(list1, list2, list3):
    print(a, b, c)
Animal #0 is dog
Animal #1 is cat
Animal #2 is bobcat
dog puppy bazooka
cat kitten tank
bobcat bobkitty lazer

A lot of things can be put into for loops to create magic:

In [27]:
names = {'House':'Grumpy', 'Wilson':'Considerate'}
for n in names:
    print('{0} is {1}.'.format(n, names[n].lower()))
House is grumpy.
Wilson is considerate.

Here, we declared a dictionary. Then, our for loop created an iterator out of the dictionary keys.

Finally, there are these amazing things called list comprehensions, where you can put a psuedo-for loop into a list and the magic happens.

In [28]:
a = [x**2 for x in range(6)]
print(a)
[0, 1, 4, 9, 16, 25]

This is amazing and should be used often.

Functions

Functions are easy to declare and use in Python. Let's declare one and dissect the pieces.

In [29]:
def example(arg1, arg2, kwarg1='Default', kwarg2=None):
    '''Use docstrings every time!'''
    print('Our first required argument is ', arg1)
    print('Our first keyword argument is ', kwarg1)
    if kwarg2:
        print('Second keyword set!')
    return 1, 2, 'dog'
string = 'Remember, ending the tabbed environment closes suites.'
In [30]:
help(example)
Help on function example in module __main__:

example(arg1, arg2, kwarg1='Default', kwarg2=None)
    Use docstrings every time!

In [31]:
a, b, c = example('ARG1', 'ARG2', kwarg2='ARG3')
Our first required argument is  ARG1
Our first keyword argument is  Default
Second keyword set!
In [32]:
group = example('ARG1', 'ARG2', 'ARG3', 'ARG4')
print(group)
Our first required argument is  ARG1
Our first keyword argument is  ARG3
Second keyword set!
(1, 2, 'dog')

As you can see, you can declare any number of required arguments (args) and any number of keyword arguments (kwargs). Regular args must be given and in the correct order. Keyword arguments (kwargs) are optional and can either be listed in the correct number and order OR be given in any arbitrary order so long as the keyword is supplied. Any number of values can be returned, and they can be assigned individually or as a grouped tuple. ALWAYS, ALWAYS USE A DOCSTRING. The block string after the declaration is your function's documentation! Try typing "example?" in IPython!

Don't forget that functions are objects! You can hand a function to a function as an argument, put them in lists and dictionaries, so on and so forth.

In [33]:
def ex2(func):
    print('Func is ', func)
ex2(example)
Func is  <function example at 0x1049477b8>

Because functions are their own objects, you can set attributes and new methods. This mechanism allows them to act as new name spaces.

In [34]:
dog = 'woof'
ex2.dog='bark'
print(dog, ex2.dog) # The two variables do not overwrite because they are in different name spaces.
woof bark

Lastly, functions can be defined in line using the lambda command. Let's create a simple function, then see how lambda makes it faster to declare.

In [35]:
def square(x):
    return x**2
sqr = lambda x: x**2
    
print(square(4))
print(sqr(4))
16
16

The lambda syntax is useful for quick function declarations, especially when you want to create a function directly in a list or dictionary. They are also very nice for list comprehensions!

In [36]:
funcs = [lambda x: x**2, lambda x: 2.*x**2+5, lambda x: 3./4. * x**0.5]
for f in funcs:
    print(f(25))
625
1255.0
3.75