Basic Programming Constructs · calling, writing, and testing your own logic
In your own words: what is a function, and why would you write one? You've already been calling functions like print() and type(), think about what they have in common.
Every function you've called so far, print(), type(), len(), was written by someone else.
Now you'll write your own: a named, reusable piece of code that takes some inputs and (usually) hands back a result.
def.
def creates a new function named cubed. It isn't run yet, Python just remembers it.
The function only runs when it is called, once per print(cubed(…)) below.
Line 1 only defines cubed; nothing runs until line 3 calls it with 2, and each call jumps back into line 2 to compute its own result before print() shows it.
def cubed(n):return statement immediately ends the function and hands back that value to the caller.return, Python automatically returns None.def cubed(n):
return n ** 3 # body: 1 indented line
def f(x, y):
return x + y
print(f(2, 3))
What are the parameters, and what are the arguments?
2 and 3 are parameters, and x and y are arguments.2 and 3 are both the parameters and arguments.x and y are parameters, and 2 and 3 are arguments.x and y are both the parameters and arguments.
sumOfSquares takes two parameters, x and y.
When you call it, you must supply an argument for each parameter, in the same order they were defined.
def sumOfSquares(x, y):
return x**2 + y**2
print(sumOfSquares(3, 4))
A function doesn't have to take any parameters. But the parentheses are still required, both when you define it and when you call it.
def bigNumber():
return 78238477823487248727834
print(bigNumber()) # still need ()
Separate the returned values with a comma. When you call the function, you can unpack them into that many variables at once.
True or False: the body of a function can only contain one line.
double(n) is defined below. Each line on the next slide calls it and stores the result in a variable.
Work out what each call returns, then drag (or click, then click the box) the matching value chip into the box.
Later calls build on earlier results, so trace them in order.
All three calls traced correctly. A function call is a value, whatever it returns, and that value can feed straight into the next call.
def, a name, parenthesized parameters, and a colon; the indented lines after it are the body.return ends the function immediately and hands back a value; no return means the function hands back None.
print() and return both seem to "give you back" a value, so it's tempting to mix them up.
They do very different jobs: one displays text in the console, the other hands a value back to whoever called the function.
cubed prints its answer instead of returning it. Calling it on its own line still shows 8 in the console, so at first glance it seems to work.
cubed(2) still prints 8 from inside the function, but then the outer print() also prints whatever cubed returned.
Two separate prints: the one inside the function, and the one printing what the function returned.
Store the result and try to use it. x is None, and you cannot add 1 to None.
def cubed(x):
return x**3 # fixed
x = cubed(2)
print(x + 1) # 9, whew!
print has no effect on a return value. Printing something inside a function only sends text to the console, it never sends a value back to the caller.
If you want the caller to be able to use the result, you must return it.
def f(x):
x += 10
print(f(10))
What will this code print?
20f doesn't print or return.None10print() displays text in the console; return hands a value back to whoever called the function.return statement always hands back None, even if it prints something.None result like a number or string, your code will crash.A variable exists in a specific scope, based on where it was defined. Use its name outside that scope, and Python won't recognize it. For now we'll consider two scopes: local and global.
def f(x): # x is local to f
return x + 5
x only exists while f is running. Once f(4) returns, x is gone, it was never defined outside of f in the first place.
x, those are different variables, even though they share a name.xxChanging one never affects the other, they live in different scopes.
A variable assigned outside any function definition is a global variable. It has global scope: it can be used anywhere, including inside functions.
True or False: parameters are local variables.
True or False: even if two functions each define a local variable x, these are still different variables.
A helper function is a function like any other, except its job is to do part of the work for a different function. Breaking a problem into smaller, named pieces is one of the most effective habits you can build as a programmer.
largerOnesDigit doesn't compute a ones digit itself, it delegates that to onesDigit, its helper.
def onesDigit(n):
return abs(n) % 10
def largerOnesDigit(x, y):
return max(onesDigit(x), onesDigit(y))
print(largerOnesDigit(134, 672)) # 4
True or False: you can write multiple helper functions for one function.
You will spend more time debugging, finding and fixing bugs, than actually writing new code. A test function is one of the best tools you have to make that easier.
testOnesDigit's only job is to check that onesDigit works. Each assert is a test case checking one specific input.
def onesDigit(n):
return n % 10
def testOnesDigit():
assert(onesDigit(5) == 5)
assert(onesDigit(123) == 3)
assert(onesDigit(100) == 0)
print("Passed!")
testOnesDigit() # Passed!
assert statement does nothing at all, and execution just continues to the next line.
assert statement crashes immediately with an AssertionError, telling you that test case failed.
Run this version, with assert(onesDigit(-123) == 3) added. It crashes! That's a good thing, we now know
onesDigit had a bug the earlier test cases never caught.
def onesDigit(n):
return n % 10
def testOnesDigit():
assert(onesDigit(5) == 5)
assert(onesDigit(123) == 3)
assert(onesDigit(100) == 0)
assert(onesDigit(999) == 9)
assert(onesDigit(-123) == 3) # we just added this case
print('Passed!')
testOnesDigit()
% and negative numbers
Try onesDigit(-123) in the console: it returns 7, not 3.
% behaves surprisingly on negative numbers: -3 % 10 is 7, not 3.
def onesDigit(n):
return abs(n) % 10
Run it and watch all five assertions pass. This doesn't guarantee onesDigit works for every possible input,
but a thoughtful set of test cases gives you strong confidence that it does.
def onesDigit(n):
return abs(n) % 10 # fixed
def testOnesDigit():
assert(onesDigit(5) == 5)
assert(onesDigit(123) == 3)
assert(onesDigit(100) == 0)
assert(onesDigit(999) == 9)
assert(onesDigit(-123) == 3)
print('Passed!')
testOnesDigit()
Plain assert tells you which case failed and what you expected, but not what your code actually returned.
@testFunction, from cmu_cpcs_utils, fixes that, and prints "Passed!" for you automatically.
@ makes this a function decorator, it modifies the behavior of the function defined right below it. More on those later.from cmu_cpcs_utils import testFunction
@testFunction
def testOnesDigit():
assert(onesDigit(5) == 5)
...
What will happen in the previous example if we remove @testFunction?
assert statements, test cases, to check that another function returns the right result.AssertionError, that's a good thing, it means you found a bug.@testFunction from cmu_cpcs_utils shows the incorrect result on failure, and prints "Passed!" for you.Python comes with plenty of built-in functions. We won't cover anywhere near all of them, and you shouldn't go hunting for the perfect Python function to shortcut an exercise. The goal of this course is for you to become comfortable writing your own solutions, so use these sparingly.
type() and isinstance()
You already know type(value). isinstance(value, t) asks a yes/no question instead: does this value have type t?
'12' * 5 do?
Predict the output first, then run it. s is a string, so * doesn't multiply like a number,
it builds a new string out of that many copies of the original.
s = '12'
print(type(s)) # predict: what type is s?
print(s * 5) # predict: what does this print?
int() converts a value
int('12') turns the string '12' into the integer 12. Now * does ordinary multiplication.
int() has its limitsWhat is int(12.8)?
121312.8What is int('two')?
2'two'float(), str(), bool()
Every type has its own conversion function: float('2.5') is 2.5, and str(2.5) is '2.5'.
bool() is the most surprising one: it's False for 0, 0.0, and '' (the empty string), and True for every non-zero number and non-empty string.
Drag (or click, then click the bin) each value chip into Truthy or Falsy, based on what bool() would return.
A couple of these look tricky at first: check whether the value is actually 0, 0.0, or '', not just whether it looks small or empty.
All 8 values sorted. bool() is False only for 0, 0.0, and the empty string, everything else is truthy.
abs(), min(), max()abs(n) returns the absolute value of n.min() and max() return the smallest or largest of their arguments, and can take more than two: min(5, 1, 7) is 1.print(abs(-5))
print(max(2, 3))
print(min(2, 3))
Click through and predict each line's output before it's revealed. Pay attention to line 2: does abs() care whether the argument is an int or a float?
Run the same five lines for real and compare the output against what you traced on the last slide.
print(abs(-5))
print(abs(-12) == abs(-12.0))
print(min(2, 3.5))
print(max(2, 3.5))
print(min(5, 1, 7))
pow() and round()pow(2, 3) is the same as 2 ** 3. It's redundant, stick with **.round() does not always round to the nearest integer. On a halfway value like 1.5 or 2.5, it rounds to the nearest even integer.round(). We'll give you a better alternative next.
Predict both lines, then run them. round(2.5) probably won't be what you expect, that's the "round-half-to-even" behavior in action.
print(round(1.5))
print(round(2.5))
math.floor(n) and math.ceil(n) return the integers just below and just above n.==. Use almostEqual(x, y) from cmu_cpcs_utils instead.math.isclose(x, y). It has some gotchas, so stick with almostEqual, it will work the way you expect.rounded() from cmu_cpcs_utils, not the builtin round().math.isclose a safe substitute?True or False: math.isclose and almostEqual work identically, just like pow and ** do.
math.isclose() breaks down for values near 0: math.isclose(0.1 + 0.2 - 0.3, 0) is False. Stick with almostEqual.
isinstance(value, t) checks a type; int(), float(), str(), and bool() convert between types.bool() is False only for 0, 0.0, and '', everything else is truthy.pow() and round(); use ** and cmu_cpcs_utils.rounded() instead.==, use almostEqual(), not math.isclose().
"IO" stands for input and output. The console is a text area where a program's output goes, and where a user can type input back to it.
We've used print() for output already; now let's round it out, and meet input().
print() accepts any number of comma-separated values, and prints them on one line, each separated by a single space.
x = 'a'
y = 'b'
z = 'c'
print(x, y, z)
What does this print?
'a b c'a, b, cabca b cinput() always returns a string
input(prompt) shows prompt, waits for the user to type something and press enter, then returns exactly what they typed.
Whatever they typed, even digits, comes back as a str, never a number.
name = input('Enter your name: ')
print('Your name is', name)
Run this and enter 5 for the dog's age. Instead of 35, you'll see 5555555: since dogYears is a string, 7 * dogYears repeats the string, it doesn't multiply a number.
dogYears = input("Enter your dog's age in years: ")
humanYears = 7 * dogYears
print("Your dog's age in human years is", humanYears)
Convert the result of input() before doing math with it. Now 7 * dogYears is ordinary multiplication.
dogYears = int(input("Enter your dog's age in years: "))
humanYears = 7 * dogYears
print("Your dog's age in human years is", humanYears)
We used double quotes here because the prompt text itself contains an apostrophe, in dog's.
What will happen if you don't input a number in the fixed example?
As functions get more complex, add print statements inside them to check local variables while a failing test case runs. One line will surprise you, and that's usually where the bug is.
print(x) just shows a bare value, with no label.print('x:', x) labels the value, so you know which variable produced it.Remove your debugging prints before you submit code to the autograder.
print() can take multiple comma-separated values, and joins them with a single space.input(prompt) shows the prompt and always returns the user's text as a str, even if it looks like a number.int() or float() before doing math with input, or you'll repeat strings instead of computing.print('x:', x), are one of the most effective debugging tools you have.def, parameters, a body, and return, the value a call evaluates to.return hands a value back to the caller.assert, are how you catch the bugs a thin set of examples would miss.