Booleans


In [ ]:
# Let's declare some bools

spam = True
print spam

print type(spam)


eggs = False
print eggs

print type(eggs)

 Python truth value testing

  • Any object can be tested for truth value
  • Truth value testing is used in flow control or in Boolean operations
  • All objects are evaluated as True except:
    • None (aka. null)
    • False
    • Zero of any numeric type: 0, 0L, 0.0, 0j, 0x0, 00
    • Any empty sequence or mapping: '', [], (), {}
    • Instances of user-defined classes implementing nonzero or len method and returning 0 or False

In [ ]:
# Let's try boolean operations

print True or True
print True or False
print False or True   # Boolean or. Short-circuited, so it only evaluates the second argument if the first one is False

In [ ]:
print True and True
print True and False
print False and True  # Boolean or. Short-circuited, so it only evaluates the second argument if the first one is True

In [ ]:
print not True
print not False

In [ ]:
# So, if all objects can be tested for truth, let's try something different

spam = [1.2345, 7, "x"]
eggs = ("a", 0x07, True)
fooo = "aeiou"

In [ ]:
print spam or eggs

In [ ]:
# Did you expect it to print True?
print fooo or []
print "" or eggs
print spam and eggs

In [ ]:
print fooo and ()
print [] and eggs
print not spam
print not ""

In [ ]:
print spam and eggs or "abcd" and False
print (spam and eggs) or ("abcd" and False)
print spam and (eggs or "abcd") and False
print spam and (eggs or "abcd" and False)

Python boolean operands:

  • ALWAYS return one of the incoming arguments!
    • x or y => if x is false, then y, else x
    • x and y => if x is false, then x, else y
    • not x => if x is false, then True, else False
  • They are short-circuited, so second argument is not always evaluated
  • Can take any object type as arguments
    • Even function calls, so boolean operands are used for flow control
  • Parentheses may be used to change order of boolean operands or comparissons

What about comparisons?


In [ ]:
spam = 2
eggs = 2.5

print spam == 2         # equal

print spam != eggs      # not equal

In [ ]:
print spam >= eggs      # greater than or equal

print spam > eggs       # strictly greater than

print spam <= eggs      # less than or equal

print spam < eggs       # strictly less than

In [ ]:
print spam is 2         # object identity, useful to compare with None (discussed latter)

print spam is not None  # negated object identity

Flow Control

Let's start with the conditional execution


In [ ]:
spam = [1, 2, 3]  # True
eggs = ""         # False

if spam:
    print "spam is True"
else:
    print "spam is False"

print "outside the conditional"  # Notice that theres is no closing fi statement

In [ ]:
if spam:
    print "spam is True"
else:
    print "spam is False"

    print "still inside the conditional"

 REMEMBER:

  • Indentation is Python's way of grouping statements!!
    • Typically four spaces per indentation level
    • No curly brackets { } or semicolons ; used anywhere
    • This enforces a more readable code

In [ ]:
if eggs:
    print "eggs is True"
elif spam:
    print "eggs is False and spam is True"
else:
    print "eggs and spam are False"

In [ ]:
if eggs:
    print "eggs is True"
elif max(spam) > 5:
    print "eggs is False and second condition is True"
elif len(spam) == 3 and not eggs is None:
    print "third condition is true"
else:
    print "everything is False"

Let's see the ternary operator


In [ ]:
spam = [1, 2, 3]  # True
eggs = ""         # False

print "first option" if spam else "second option"

In [ ]:
print "first option" if eggs else "second option"

In [ ]:
print "first option" if eggs else "second option" if spam else "last option"  # We can even concatenate them

In [ ]:
print "first option" if eggs else ("second option" if spam else "last option")

Time for the while loop


In [ ]:
spam = [1, 2, 3]
while len(spam) > 0:
    print spam.pop(0)

In [ ]:
spam = [1, 2, 3]
idx = 0
while idx < len(spam):
    print spam[idx]
    idx += 1

What about the for loop?


In [ ]:
spam = [1, 2, 3]
for item in spam:        # The for loop only iterates over the items of a sequence
    print item

In [ ]:
spam = [1, 2, 3]
for item in spam[::-1]:  # As we saw, slicing may be slow. Keep it in mind
    print item

In [ ]:
eggs = "eggs"
for letter in eggs:      # It can loop over characters of a string
    print letter

In [ ]:
spam = {"one": 1,
        "two": 2,
        "three": 3}
for key in spam:         # Or even it can interate through a dictionary
    print spam[key]      # Note that it iterates over the keys of the dictionary

Let's see how to interact with loops iterations


In [ ]:
spam = [1, 2, 3]
for item in spam:
    if item == 2:
        break
    print item
  • break statement halts a loop execution (inside while or for)
  • Only affects the closer inner (or smallest enclosing) loop

In [ ]:
# A bit more complicated example
spam = ["one", "two", "three"]
for item in spam:                 # This loop is never broken
    for letter in item:
        if letter in "wh":        # Check if letter is either 'w' or 'h'
            break                 # Break only the immediate inner loop
        print letter
    print                         # It prints a break line (empty line)

In [ ]:
# A bit different example
spam = ["one", "two", "three"]
for item in spam:
    for letter in item:
        if letter in "whe":       # Check if letter is either 'w', 'h' or 'e'
            continue              # Halt only current iteration, but continue the loop
        print letter
    print
  • continue statement halts current iteration (inside while or for)
  • loops continue its normal execution

In [ ]:
spam = [1, 2, 3, 4, 5, 6, 7, 8]
eggs = 5
while len(spam) > 0:
    value = spam.pop()
    if value == eggs:
        print "Value found:", value
        break
else:                                      # Note that else has the same indentation than while
    print "The right value was not found"

In [ ]:
spam = [1, 2, 3, 4, 6, 7, 8]
eggs = 5
while len(spam) > 0:
    value = spam.pop()
    if value == eggs:
        print "Value found:", value
        break
else:
    print "The right value was not found"

else clause after a loop is executed if all iterations were run without break statement called


In [ ]:
spam = [1, 2, 3]
for item in spam:
    pass

pass statement is Python's noop (does nothing)

Let's check exception handling


In [ ]:
spam = [1, 2, 3]
try:
    print spam[5]
except:             # Use try and except to capture exceptions
    print "Failed"

In [ ]:
spam = {"one": 1, "two": 2, "three": 3}
try:
    print spam[5]
except IndexError as e:      # Inside the except clause 'e' will contain the exception instance
    print "IndexError", e
except KeyError as e:        # Use several except clauses for different types of exceptions
    print "KeyError", e

In [ ]:
try:
    print 65 + "spam"
except (IndexError, KeyError) as e:  # Or even group exception types
    print "Index or Key Error", e
except TypeError as e:
    print "TypeError", e

In [ ]:
try:
    print 65 + 2
except (IndexError, KeyError), e:
    print "Index or Key Error", e
except TypeError, e:
    print "TypeError", e
else:
    print "No exception"           # Use else clause to run code in case no exception was raised

In [ ]:
try:
    print 65 + "spam"
    raise AttributeError           # Use 'raise' to launch yourself exceptions
except (IndexError, KeyError), e:
    print "Index or Key Error", e
except TypeError, e:
    print "TypeError", e
else:
    print "No exception"
finally:
    print "Finally we clean up"    # Use finally clause to ALWAYS execute clean up code

In [ ]:
try:
    print 65 + 2
except (IndexError, KeyError), e:
    print "Index or Key Error", e
    raise                          # Use 'raise' without arguments to relaunch the exception
except TypeError, e:
    print "TypeError", e
else:
    print "No exception"
finally:
    print "Finally we clean up"    # Use finally clause to ALWAYS execute clean up code

Let's see another construction


In [ ]:
try:
    f = open("tmp_file.txt", "a")
except:
    print "Exception opening file"
else:
    try:
        f.write("I'm writing to a file...\n")
    except:
        print "Can not write to a file"
    finally:
        f.close()

Not pythonic, too much code for only three real lines


In [ ]:
try:
    with open("tmp_file.txt", "a") as f:
        f.write("I'm writing to a file...\n")
except:
    print "Can not open file for writing"

Where is the file closed? What happens if an exception is raised?

Python context managers

  • Encapsulate common patterns used wrapping code blocks where real runs the program logic
    • Usually try/except/finally patterns
  • Several uses:
    • Automatic cleanup, closing files or network or DB connections when exiting the context block
    • Set temporary environment, like enable/disable logging, timing, profiling...
  • Use the 'with' and optionally the 'as' statements to open a context manager
    • It is automatically closed when code execution goes outside the block

Comprehension


In [ ]:
spam = [0, 1, 2, 3, 4]
eggs = [0, 10, 20, 30]
fooo = []

for s in spam:
    for e in eggs:
        if s > 1 and e > 1:
            fooo.append(s * e)

print fooo

Short code, right?


In [ ]:
spam = [0, 1, 2, 3, 4]
eggs = [0, 10, 20, 30]
fooo = [s * e for s in spam for e in eggs if s > 1 and e > 1]
print fooo

What about now?


In [ ]:
fooo = [s * s for s in spam]  # This is the most basic list comprehension construction
print fooo

In [ ]:
fooo = [s * s for s in spam if s > 1]  # We can add 'if' clauses
print fooo

In [ ]:
spam = [1, 2, 3, 4]
eggs = [0, -1, -2, -3]
fooo = [l.upper() * (s + e) for s in spam
        for e in eggs
        for l in "SpaM aNd eGgs aNd stuFf"
        if (s + e) >= 1
        if l.islower()
        if ord(l) % 2 == 0]                 # We can add lots of 'for' and 'if' clauses
print fooo

In [ ]:
spam = [1, 2, 3, 4]
eggs = [10, 20, 30, 40]
fooo = [[s * e for s in spam] for e in eggs]  # It is possible to nest list comprehensions
print fooo
  • List comprehension is faster than standard loops (low level C optimizations)
  • However, built-in functions are still faster (see Functional and iterables tools module)

There is also dict comprehension (2.7 or higher)


In [ ]:
spam = ['monday', 'tuesday',
        'wednesday', 'thursday',
        'friday']
fooo = {s: len(s) for s in spam}  # The syntax is a merge of list comprehension and dicts
print fooo

In [ ]:
spam = [(0, 'monday'), (1, 'tuesday'),
        (2, 'wednesday'), (3, 'thursday'),
        (4, 'friday')]
fooo = {s: idx for idx, s in spam}  # Tuple unpacking is useful here
print fooo

In [ ]:
spam = ['monday', 'tuesday',
        'wednesday', 'thursday',
        'friday']
fooo = {s: len(s) for s in spam if s[0] in "tm"}  # Ofc, you can add more 'for' and 'if' clauses
print fooo