Today: loose ends - truthy logic, comprehension, better/shorter code, #! line, float flaws

Truthy True/False

The if and while are actually a little more flexible than we have shown thus far. They use the "truthy" system to distinguish True/False.

You never need to use this in CS106A, just mentioning it in case you see it in the future.

For more detail see "truthy" section in the if-chapter Python If - Truthy

Truthy False

Truthy logic says that "empty" values count as False. The following values, such the empty-string and the number 0 all count as False in an if-test:

# All count as False:
None
''
0
0.0
[]
{}

Truthy True

Any other value counts as True. Anything that is not one of the above False values:

# Count as True:
6
3.14
'Hello'
[1, 2]
{1: 'b'}

Truthy bool()

The bool() function takes any value and returns a formal bool False/True value, so for any value, it reveals how Truthy logic will treat that value. You don't typically use bool() in production code. Here we're using it to see Python's internal logic.

>>> # "Empty" values count as False
>>> bool(None)
False
>>> bool('')
False
>>> bool(0)
False
>>> bool(0.0)
False
>>> bool([])
False 
>>> # Anything else counts as True
>>> bool(6)
True
>>> bool('yo')
True
>>> bool([1, 2])
True

Truthy Quiz

These are tricky / misleading. Can always try it in the interpreter.

>>> bool(7)
True
>>> bool('')
False
>>>
>>> bool(0)
False
>>>
>>> bool('hi')
True 
>>>
>>> bool('False')   # SAT Vibes
True 
>>>
>>> bool(False)
False
>>>
>>>

How To Use Truthy

With truthy-logic, you can use a string or list or whatever as an if-test directly. This makes it easy to test, for example, for an empty string like the following. Testing for "empty" data is such a common case, truthy logic is a shorthand for it. For CS106A, you don't ever need to use this shorthand, but it's there if you want to use it. Also, many other computer languages also use this truthy system, so we don't want you to be too surprised when you see it.

# Pre-truthy code:
if word != '':
    print(word)


# Truthy equivalent:
if word:
    print(word)

Truthy Shortcut

Truthy logic is just a shortcut test for the empty string or 0 or None. Those are pretty common things to test for, so the shortcut is handy. You do not need to use this shortcut in your writing, but you may see it in when reading code. Most computer languages use Truthy logic like this, not just Python.

(optional) Truthy Example/Exercise

> no_zero

> not_empty


Better / Shorter Code

Look at a technique to make code shorter and better.

Preface: Variable Set Default Pattern

Say we want to set alarm differently for weekends, something like this:

if is_weekened:
    alarm = 'off'
else:
    alarm = '9:00 am'

The above code is fine. Here I will propose a slightly shorter way, and this is used below. (1) Initialize (set) the variable to its common, default value first. (2) Then an if-statement detects the case where the var should be set to a different value.

alarm = '9:00 am'
if is_weekend:
    alarm = 'off'

Strategy: Better/Shorter - Unified Lines

if case-1:
    lines-a
    ...
    ...

if case-2:
    lines-b
    ...
    ...

grounded() Example

> grounded()

grounded(minutes, is_birthday): Given you came home minutes late, how many days of grounded are you. If minutes is an hour or less, grounding is 5, otherwise 10. Unless it is your birthday, then 30 extra minutes are allowed. Challenge: change this code to be shorter, not have so much duplicated code.

The code below works correctly. You can see there is one set of lines for the birthday case, and another set of similar lines for the not-birthday case. What exactly is the difference between these two sets of lines?

def grounded(minutes, is_birthday):
    if not is_birthday:
        if minutes <= 60:
            return 5
        return 10
    
    # is birthday
    if minutes <= 90:
        return 5
    return 10

Unify Cases Solution

grounded() Better Unified Solution

1. Set limit first. 2. Then unified lines below use limit, work for all cases.

def grounded(minutes, is_birthday):
    limit = 60
    if is_birthday:
        limit = 90
    
    if minutes <= limit:
        return 5
    return 10

Variables vs. Code

An interesting feature of the unified solutions, is that we add a variable in order to reduce the amount of code. We think of variables and code as two different things, but there's some exchangeability we're leveraging here.

(optional) match()

> match()

match(a, b): Given two strings a and b. Compare the chars of the strings at index 0, index 1 and so on. Return a string of all the chars where the strings have the same char at the same position. So for 'abcd' and 'adddd' return 'ad'. The strings may be of any length. Use a for/i/range loop. The starter code works correctly. Re-write the code to be shorter.

match():
 'abcd'
 'adddd'  -> 'ad'
  01234

Code before unify:

def match(a, b):
    result = ''
    if len(a) < len(b):
        for i in range(len(a)):
            if a[i] == b[i]:
                result += a[i]
    else:
        for i in range(len(b)):
            if a[i] == b[i]:
                result += a[i]
    return result

match() Unified Solution

def match(a, b):
    result = ''
    # Set length to whichever is shorter
    length = len(a)
    if len(b) < len(a):
        length = len(b)

    for i in range(length):
        if a[i] == b[i]:
            result += a[i]

    return result

List Comprehensions

List comprehensions are a beautiful Python feature. For certain problems, they are a short and unbeatable solution. If you are interviewing for an internship, and let slip that you like comprehensions, you will get a knowing nod from the interviewer, like "this kid gets it." (Not needed for any of our homeworks.)

[1, 2, 3, 4] -> [1, 4, 9, 15]

Comprehension Syntax 1

Comprehension 1-2-3

>>> nums = [1, 2, 3, 4, 5, 6]
>>> [n * n for n in nums]
[1, 4, 9, 16, 25, 36]
>>> [n * -1 for n in nums]
[-1, -2, -3, -4, -5, -6]
>>>
>>> strs = ['the', 'donut', 'of', 'destiny']
>>> [s.upper() for s in strs]
['THE', 'DONUT', 'OF', 'DESTINY']
>>> [str(n) + '!!' for n in nums]
['1!!', '2!!', '3!!', '4!!', '5!!', '6!!']

(optional) Try In Interpreter

>>> nums = [1, 2, 3, 4, 5, 6]
>>> [n + 10 for n in nums]
[11, 12, 13, 14, 15, 16]
>>> [abs(n - 3) for n in nums]
[2, 1, 0, 1, 2, 3]

Comprehension Examples / Exercises

Section on server: Comprehensions

> power2()

> diff21()

> make_upper()

Comprehension + If

>>> nums = [1, 2, 3, 4, 5, 6]
>>> [n for n in nums if n > 3]
[4, 5, 6]
>>> [n * n for n in nums if n > 3]
[16, 25, 36]

(optional) Comprehension-If Exercises

These are all 1-liner solutions with comprehensions.

Syntax reminder - e.g. make a list of nums doubled where n > 3

[2 * n for n in nums if n > 3]

> even_ten (has if)

> up_only (has if)

Comprehensions Replace map()

Comprehensions are easier to write than map(), so you can use them instead. Why did we learn map() then? Because map() is the ideal way to see how lambda works, and lambda is a programming fundamental you should know. However, at this point, you can use comprehensions instead of map(), (and exam problems will give full credit to either form, your choice).

Avoid Comprehension Mania - 1 Line Is Ideal

Programmers can get into Comprehension Fever - trying to write your whole program as nested comprehensions. Or it may be a spirit of one-upmanship, like shrinking down the code more than everyone else. However, using comprehensions for everything is a mistake. Comprehensions are so dense, they can be unreadable if too long. Probably 1-line is the sweet spot for a comprehension.

Using regular functions, loops, variables etc. for longer phrases is fine.


Explain Glossed Over Python Lines - see
wordcount.py / pylibs.py

There are lines of Python code we have glossed over. Today we will explain these.

Usual practice: to create a new Python file, copy an existing Python file you have laying around. In this way, you get the #!/usr/bin... and other bits of rote syntax mentioned here.

What is up with that very first line: #!..

#!/usr/bin/env python3

"""
Stanford CS106A Pylibs Example
...

1. #!/usr/bin/env python3

2. #!/usr/bin/python - python2

This is an older form of the first line.

This first line specifies that the file is Python 2 code

#!/usr/bin/python

import sys
...

There are not huge differences between Python version-2 and version-3. You could easily write Python-2 code if you needed to, but Python-3 is strongly preferred for all new work.

Legacy code - that said, many orgs may have old "legacy" python-2 programs laying around, and it's easiest if they just use them and don't update or edit them. The first line #!/usr/bin/env python3 is a de-facto way of marking which version the file is for.

3. End With Boilerplate If-Statement

You do not need to remember all those details. Just remember this: have that if-statement at the bottom of your file as a couple boilerplate lines. It calls the main() function when this file is run on the command line.

...
... python file ..
...

if __name__ == '__main__':
    main()

Run foo.py From Command Line

When you run a program from the command line like this, Python loads the whole file, and then finally calls its main() function.

$ python3 pylibs.py

The if-statement shown above is the bit of code that calls main(). It's a historical quirk that Python does not simply call main() automatically, but it doesn't, so we have this if-statement at the bottom of the file.

Typically, when starting a new Python project, you copy a Python file you have laying around. In this way, you get the boilerplate #!/usr/bin.. line at the start, and this if-main line at the end of the file.

(optional) Why Do We Need This If-statement?

Consider a Run of the Program

Say you run a program like this:

$ python3 wordcount.py poem.txt

In that case, the if __main__ expression will be True. What does it do? It calls the main() function. So if the python file is run from the command line, call its main() function. That is the behavior we want, and it is what the above "if" does.

What Is The Other Way To Load?

What is the other way to load a python file? There is some other python code, and that code imports the python file.

# In some other Python file
# and it imports wordcount
...
import wordcount

In this more unusual case, the above "if" will be False. Loading a python file (module) does not run its main(). So the if-statement runs main() when the python file is itself run from the command line, but does not run main() when the file is imported by another file.


Two Math Systems, "int" and "float" (Recall)

# int
3  100  -2

# float, has a "."
3.14  -26.2  6.022e23

Math Works, but (clickbat):
Float Has This One Crazy Flaw

Abstract Math vs. Applied Arithmetic

alt:abstract math vs. applied arithmetic are different e.g. 1-third vs. 0.3333

Crazy Flaw Demo - Adding 1/10th

Note: do not panic! We can work with this. But it is shocking.

What is happening here?

>>> 0.1
0.1
>>> 0.1 + 0.1
0.2
>>> 0.1 + 0.1 + 0.1    # this is why we can't have nice things
0.30000000000000004
>>> 
>>> 0.1 + 0.1 + 0.1 + 0.1
0.4
>>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1
0.5
>>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1
0.6
>>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1
0.7
>>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1
0.7999999999999999     # here the garbage is negative
>>> 7 * 0.1
0.7000000000000001

Another example with 3.14

>>> 3.14 * 3
9.42
>>> 3.14 * 4
12.56
>>> 3.14 * 5
15.700000000000001   # d'oh

Float Garbage Digits

Conclusion: float math is slightly wrong

Why Must We Have This Garbage?

The short answer, is that with a fixed number of bytes to store a floating point number in memory, there are some unavoidable problems where numbers have these garbage digits off to the right. It is similar to the impossibility of writing the number 1/3 precisely as a decimal number — 0.3333 is close, but falls a little short.

Crazy, But Not Actually A Problem

Must Avoid One Thing: no ==

>>> a = 3.14 * 5
>>> b = 3.14 * 6 - 3.14
>>> a == b   # Observe == not working right
False
>>> b
15.7
>>> a
15.700000000000001

How To Compare Floats

>>> a = 3.14 * 5
>>> b = 3.14 * 6 - 3.14
>>>
>>> import math
>>> math.isclose(a, b)
True
>>>
>>> abs(a - b) < 0.0001
True
>>>

int Arithmetic is Exact

>>> # Int arithmetic is exact
>>> # The two expressions are exactly equal, "precise"
>>> 2 + 3
5
>>> 1 + 1 + 3
5
>>> 
>>> 2 + 3 == 1 + 1 + 3
True
>>>

Doesn't seem like such a high bar .. and yet float does not give us this!

int Bank Balance

Bank balance example: Float is not a good choice for bank balances. Customers do not want to see math a little bit off. Bank balances can be stored as int number of pennies. That way, adding and subtracting from one account to another comes out exactly right and balanced.

# balance is $ 457.12
# store as int pennies
bal = 45712

# withdraw $10, i.e. 1000 pennies
bal -= 1000

# balance is exactly right
bal == 44712

# when printing, put in the '.'
'447.12'

This also shows the "illusion" quality of some computer programs - the data is stored one way within the computer, but we adjust it to another way whenever we show it to the user. The user cannot tell this is happening, as the data is consistent when they see it. This is a good technique if one format works best within the computer, but a different format makes the most sense of the user to see and think about.

int Bitcoin

In fact, Bitcoin wallets use exactly this int strategy - the amount of bitcoin in a wallet is measured in "satoshis". One satoshi is one 100-millionth of 1 bitcoin. Each balance is tracked as an int number of satoshis, e.g. an account with 0.25 Bitcoins actually has 25,000,000 satoshis. Using ints in this way, the addition and subtraction to move bitcoin (satoshis) from one account to another comes out exactly correct. int is precise!

# bitcoin wallet containing 0.25
# actually int 25,000,000 satoshis
bal = 25000000

# spend 0.1 bitcoin, int 10,000,000
bal -= 10000000

# balance comes out exactly right
bal == 15000000

Optional - if we have time.

Open Source - Python is an Example

You have noticed that Python works well on your machine, and yet it's free. How does that work? Python is a great example of "open source" software.

Much of the internet is based on open standards - TCP/IP, HTML, JPEG - and open source software: Python (language), Linux (operating system), R (statistics system).

How Open Source Works:

Let's look at Python
alt: python open source cycle

Open Source License - Contributions

Open Source Economics