Scripts¶

  • scripts

    • no from byubit import Bit
    • no decorator
    • no "main" function
    • every line gets run
    • functions must be defined before they are used
    • what happens when there is nothing but function definitions in a script?
    • Run from console
    • Run from PyCharm
      • Right click, run
      • Note the script name in the "configurations" menu. Now the play button works.
  • debugger

    • PyCharm layout: new frames, buttons
    • breakpoints
    • step through
    • step into
    • step out
    • show control flow in a script
  • return

    • diagrams
    • add two numbers
    • smaller of two numbers
    • boolean return (% => is_even)
    • how_many_rows using // and %
  • None

Beyond Bit¶

Writing and running python scripts

simple_script.py¶

NOTES

  • Run the script from the console: observe that each line was output
  • Note that nothing was imported, let alone Bit
    • Python simply executes each statement as it is found
  • The first line is exectued (the print statement) before the next line is executed
    • NOTE: read all the lines, then do all the work
  • Introduce an error in the script and run it
    • Some lines don't run
  • Step through each line

Then

  • Run from PyCharm
    • Right-click -> run
      • Observe the "Run" tab at the bottom becomes active
      • It shows the output from the program
      • It tells you that the program finished successfully
    • Point out the file name in the configuration menu
      • The play button runs whatever file is displayed in the configuration menu
    • Introduce an error in the script and run it
      • Note: due to a JetBrains bug, the stdout/err streams are sometimes out of sync
      • The stderr message will get printed before the stdout message
      • The correct order is always presented when you run from the terminal
        • To force PyCharm to sync them: configurations menu -> edit configurations -> select config -> execution -> Emulate terminal in output console

Then

  • Debugger
  • Run from PyCharm with no breakpoints
    • It runs "normally"
  • Add a breakpoint
    • Click in the left gutter
  • Run from PyCharm
    • Execution stops on breakpoint
    • Debug tab at the bottom
      • "Debugger" and "Console" views
        • Console shows output (same as when running)
        • Debugger shows stack trace and variables
    • Step over to go line-by-line
    • Stop to stop debugging
    • Replay to start the script over
    • Click the horizontal bar on the right to minimuze the debugger view

print_stuff.py¶

NOTES

  • Open print_stuff.py
  • Click the run button
    • simple_script.py ran instead!
    • Remember to pay attention to which file is shown in the configurations menu box
  • Right-click, run
  • Put a breakpoint on print('Bit...
    • Observe how step over does not step into the lines of the function
    • Rerun and use step into
    • Observe how we step into each function
    • Usually you don't need to step into functions you didn't write
  • Clear the first breakpoint and put one inside say_it_twice on the second statement
    • Observe how the program breaks there, inside the function
    • Observe how the stacktrace shows us that we are inside say_it_twice
    • Observe the value of the variable message
  • Clear the breakpoint and put breakpoints on print('Bit... and say_it_with_a_preamble('I love...
    • Demonstrate the continue button
    • It stops at the next breakpoint (or runs to the end of the script)

🎨 return¶

In [ ]:
def add(a, b):
    result = a + b
    return result
In [ ]:
number = add(7, 9)
print(number)

NOTES

number = add(7, 9)

Before number gets a value, python needs to evaluate add(7, 9)

So add runs with a = 7 and b = 9.

result = 7 + 9 = 16

Then add returns the value 16.

Now, add(7, 9) is replaced with its returned value 16, which is assigned to number.

In [ ]:
def add(a, b):
    return a + b
In [ ]:
number = add(3, 4)
print(number)

NOTES

  • return returns a value. You can use a variable to reference the value, or you can some other expression to describe the value.

Values and Expressions¶

Variables are assigned values.

Functions return values.

An expression is a recipe for getting a value.

When a value is needed but an expression is provided, the expression is evaluated to get a value.

An expression can be:

  • a value literal: 7, 'hello'
  • a variable reference: number
  • a function call: add(7, 8)
  • a combination of other expressions: number + 7 + add(2, 3)
In [ ]:
%%file fancy_math.py

def add_seven(number):
    return number + 7


def is_big(number):
    return number > 10


def make_smaller(number):
    if is_big(number):
        return number - 10
    else:
        return number - 1
    

def main(number):
    number = add_seven(number)
    number = make_smaller(number)
    print(number)

    
number = 7
main(number)

fancy_math.py¶

NOTES

  • Run script

    • Note that is_big returns a bool, while add_seven and make_smaller return ints.
  • Step through with a debugger

    • Click settings icon -> Show return values
    • Breakpoint on number = 7
      • Step: see that number gets the value 7
    • Note that there are many versions of number
      • Draw out number in main, add_seven, is_big, and make_smaller
      • If necessary, demonstrate how these variables can be renamed
    • Observe how the value assigned to number changes as you step over the last lines of the script
    • Rerun, stepping into each function call
      • be sure to step into the call to is_big inside make_smaller
        • Observe the stack trace
In [ ]:
%%file test_fancy_math.py
from fancy_math import add_seven, is_big


def test_add_seven():
    assert add_seven(1) == 8
    assert add_seven(10) == 17

    
def test_is_big():
    assert is_big(11)
    assert not is_big(2)

test_fancy_math.py¶

NOTES

  • Look at test_fancy_math.py
  • Explain that they will have test files that look like these in their labs from now on
  • We'll discuss them more on Friday
  • For now, know how to run the tests:
    • right click on the test file and select "Run python tests in ..."
  • Go over the test results

What does a function return when it doesn't return anything?

🤔

🎨 None¶

In [ ]:
def print_something():
    print('something')
    
result = print_something()
In [ ]:
print(result)
In [ ]:
type(result)
In [ ]:
result is None

None is the special value that means nothing.

Every variable must point to something. So, if a variable points to nothing, it points to None.

Every function must return something, so if it returns nothing, it returns None.

By default, if you don't specify a return statement in your function, it returns None.

In [ ]:
None + 1
In [ ]:
foo = None
foo / 1
If you get a TypeError referencing NoneType, it probably means you have an unexpected None popping up.

Use the debugger to figure out where the None is coming from.

🖌 Arithmetic Operators¶

Division¶

$11 \div 3$     or     $\frac{11}{3}$

In [ ]:
print(11 / 3)

Or to get just the integer part of the division:

In [ ]:
print(11 // 3)

Modulus¶

To get the remainder of division:

In [ ]:
print(11 % 3)
In [ ]:
print(25 % 2)

The // operator is the integer division operator.

The % is the modulus operator.

Perhaps when you were learning division in grade school, you started with problems like:

$ 7 \div 3 = 2 r 1 $

Seven divided by three equals two remainder one

The // operator gives you the integer quotient (the "two").

The % operator gives you the remainder (the "one").

Multiplication¶

$2 \times 5$     or     $2 \cdot 5$

In [ ]:
print(2 * 5)

Exponentiation¶

$2^5$

In [ ]:
print(2 ** 5)

Equality¶

In [ ]:
print(7 == 8)
In [ ]:
print(7 == 7)
In [ ]:
print(3 + 4 == 7)
In [ ]:
number = 3
print(number + 4 == 7)
In [ ]:
number = 3
print((number + 4) == 7)
If you are ever in doubt about the order of operations, use parentheses to make it obvious.

🧑🏻‍🎨 how_many_rows.py¶

NOTES

  • The program is not working: we need an integer number of rows, not a float.
    • While glancing at the code may be sufficient to diagnose the problem (return people / row_width), use the debugger to demonstrate how to backtrack information to the source of the problem
      • We see incorrect output. Where did that output come from?
      • Is the code block that produced the output correct? If so, where did it get the incorrect information?
      • Use the debugger to break at the call site, is the information correct? If not, where did it come from?
      • Continue to track down the source of incorrect informaton.
  • Work with a partner - what is the correct logic?
    • What test cases can we use to check this?
  • Walk through:
    • rows = people // row_width
      • Try this with 15 people and a row width of 10: do we get 2?
      • Why not? Draw it out!
    • What logic is needed to fix it? Discuss.
  • Play with starting values
    • Show how debugger steps over if block under certain conditions
In [ ]:
def how_many_rows(people, row_width):
    """Return the number of rows needed to seat the guests, given a row width."""
    rows = people // row_width

    if people % row_width != 0:
        rows = rows + 1
    
    return rows


def display_information(num_guests, row_width, rows):
    max_capacity = rows * row_width
    extra = max_capacity - guests
    
    print(f'With {guests} guests and rows of {row_width} seats:')
    print(f'  You need {rows} rows resulting in {extra} extra seats')


def main(guests, row_width):
    rows = how_many_rows(guests, row_width)
    display_information(guests, row_width, rows)
    

# Set guests and row width
guests = 223
row_width = 25

main(guests, row_width)

Key Ideas¶

  • You can write Python scripts that do something besides Bit
  • We can use the PyCharm debugger to step through our code
  • Functions can return information using return
  • Expressions are instructions to Python for creating or retrieving a value
    • expressions are evaluated
  • Python has many arithmetic operators, including + - / // % * **
  • None is the value that means nothing
  • When the code isn't doing what we expected, we can use the debugger to track down the source of the problem.