As a programmer, you often need a chunk of code in two or more program locations. No matter how often you want to use that code, you don't want to write that code repeatedly. What can you do?
Sometimes, you may realize that you have some code that would be perfect for use in another program. Can you somehow reuse that code without copying and pasting it?
You may sometimes find yourself with some complicated code that has become hard to read, understand, and maintain. What can you do to improve matters?
Most languages support the concept of procedures, blocks of code that run as separate units. In Python, we call these functions or methods.
Functions help solve all of these problems. They help reduce repetitive code, enable easy code reuse, and improve code readability and maintainability. Methods provide the same benefits as functions, but their behaviors are limited to specific objects.
Using a function means calling, invoking, executing, or running it. All of those terms mean the same thing. When Python encounters a function call, it transfers program flow to the code that comprises the function and executes that code. When the code finishes its work, the function returns a value to the code that invoked it. The calling code is free to use or ignore the return value as the programmer sees fit. Execution resumes from where the function was called.
To invoke a function, write the function's name followed by a pair of parentheses ()
. For example, when we call the hello
function in the following code, we write hello()
:
def hello():
print('Hello')
return True
hello() # invoking function; ignore return value
print(hello()) # using return value in a `print` call
The first 3 lines of this program contain the function definition or function declaration. We'll come back to function definitions a little later in this chapter. For now, notice that the function returns the value True
when it is done running.
Lines 5 and 6 show two calls to the hello
function. In the first invocation, we ignore the return value. In the second, we capture the return value and pass it to the print
function for printing.
What does the print
function return? That's easy to check:
>>> print(print())
None
We'll see why that is later.
Functions can also take one or more comma-separated arguments between the parentheses. Functions usually use arguments to modify the actions they take. A single argument is passed to the print
function on lines 2 and 6 above. In this case, it tells print
what to print.
To provide additional arguments, separate them with commas:
print('hello', 'good-bye', 'farewell')
If you have a lot of arguments (or some longish ones), you can spread them over several lines:
print(
'hello',
'good-bye',
'farewell',
'adios',
'ciao',
'auf wiedersehen',
)
Built-in functions are functions that are always available in Python. Many of these functions are used in almost every significant Python program. For instance, you should already be comfortable with the print
function.
As of Python 3.11.4, there are approximately 70 built-in functions. We've already met a number of these:
float
, int
str
, list
, tuple
set
, frozenset
input
, print
type
len
You can learn more in the Python documentation for Built-in Functions. Let's look at a few of these functions (we'll meet even more in the Core Curriculum).
min
and max
You can use the min
and max
functions to determine a collection's minimum and maximum values. The collection's objects must have an ordering that recognizes the <
and >
operators for comparing any pair of those objects.
print(min(-10, 5, 12, 0, -20)) # -20
print(max(-10, 5, 12, 0, -20)) # 12
print(min('over', 'the', 'moon')) # 'moon'
print(max('over', 'the', 'moon')) # 'the'
print(max(-10, '5', '12', '0', -20))
# TypeError: '>' not supported between instances
# of 'str' and 'int'
You can also use max
and min
with a single iterable argument, such as a list, set, or tuple:
my_tuple = ('i', 'tawt', 'i', 'taw', 'a',
'puddy', 'tat')
print(min(my_tuple)) # 'a'
print(max(my_tuple)) # 'tawt'
We'll discuss iterables in a later chapter. For now, you only need to know that strings, sequences, mappings, and sets are iterable.
ord
and chr
Given a single character, ord
returns an integer that represents the Unicode code point of that character. For the standard ASCII character sets, the code points refer to the values of the characters in the standard ASCII character set. For example:
print(ord('a')) # 97
print(ord('A')) # 65
print(ord('=')) # 61
print(ord('~')) # 126
chr
is the inverse of ord
. That is, it converts an integer to the corresponding Unicode character:
print(chr(97)) # a
print(chr(65)) # A
print(chr(61)) # =
print(chr(126)) # ~
In the remainder of this assignment, we will use the terms truthy and falsy to describe values. These are not the same thing as True
and False
, but are a little more general. The following values are said to be falsy:
False
, None
0
values (integers, floats, complex)
''
[]
, ()
, {}
, set()
, frozenset()
, and range(0)
All other values are said to be truthy.
We will revisit this concept in more detail in the next chapter. For now, you only need to be aware of the concept. You can think of truthy and falsy as True
and False
for now.
any
and all
Two very useful built-in functions are the any
and all
functions. They both operate on iterable collections, such as lists, tuples, ranges, dictionaries, and sets.
The any
function returns True
if any element in a collection is truthy, False
if all of the elements are falsy. On the other hand, all
returns True
if all of the elements are truthy, False
otherwise.
collection1 = [False, False, False]
collection2 = (False, True, False)
collection3 = {True, True, True}
print(any(collection1)) # False
print(any(collection2)) # True
print(any(collection3)) # True
print(any([])) # False
print(all(collection1)) # False
print(all(collection2)) # False
print(all(collection3)) # True
print(all([])) # True
Notice that any
returns False
for an empty collection since none of the elements are truthy. However, all
returns True
for the same collection since none of the elements are falsy (all of the elements are thus truthy).
This example may not seem very useful. However, if you have a collection of something other than Booleans, you can use a comprehension with any
or all
to test whether any or all of the elements meet a certain condition. (We'll meet comprehensions a little later.)
For instance, assume you want to determine whether a list of numbers has any even values in it. You can use a list comprehension to determine which values are even or odd:
numbers = [2, 5, 8, 10, 13]
print([number % 2 == 0 for number in numbers])
# [True False True True False]
You can take that one step further to determine whether any or all of the numbers are even:
numbers = [2, 5, 8, 10, 13]
print(any([number % 2 == 0 for number in numbers])) # True
print(all([number % 2 == 0 for number in numbers])) # False
numbers = [5, 13]
print(any([number % 2 == 0 for number in numbers])) # False
print(all([number % 2 == 0 for number in numbers])) # False
numbers = [2, 8, 10]
print(any([number % 2 == 0 for number in numbers])) # True
print(all([number % 2 == 0 for number in numbers])) # True
Some functions are intended for use in a REPL. These functions provide quick access to information about your program or Python itself.
The id
function returns an integer that serves as an object's identity. Every object has a unique identity that does not change during the object's lifetime. (The identity may be reused later in the program if the original object is discarded.)
In most cases, two instances of an object with the same value will always have two distinct identities. This is not always true, though. For instance, in a process called interning, every unique integer object from -5 through 256 has the same identity. Interning also applies to some strings:
# Paste this code into the Python REPL
# Don't run it as a program
s = 'Hello, world!'
t = 'Hello, world!'
print(id(s) == id(t)) # False
s = 'supercalifragilisticexpialidocious'
t = 'supercalifragilisticexpialidocious'
print(id(s) == id(t)) # True
x = 5
y = 5
print(id(x) == id(y)) # True
x = 5
y = 6
print(id(x) == id(y)) # False
Interning yields memory space savings and performance improvements. We discuss it mainly because new Python programmers sometimes discover this behavior and think they've found a bug. In practice, it's not an important concept, but it's worth being aware of.
By the way: the reason we asked you to paste that code into the Python REPL is that Python interns different things when you run a program file. In the above code, the first print
will output True
if you run it as a program.
Later on, we'll see why id
is useful.
Interning is an implementation detail that varies based on the flavor and version of Python you are using. While most flavors and versions intern the small integers shown above, some intern other values such as certain strings.
Python "flavors" are different implementations of Python. The flavor you are most likely using is CPython, but other flavors include Jython, PyPy, AnacondaPython, and more.
When used without arguments, the dir
function returns a list of all identifiers in the current local scope. When used with an argument, dir()
returns a list of the object's attributes (typically, the object's methods and instance variables).
>>> dir()
['__builtins__', '__name__', 'struct']
>>> dir(5)
['__abs__', '__add__', '__and__', '__bool__',
... a bunch of stuff omitted ...,
'__xor__', 'as_integer_ratio', 'bit_count',
'bit_length', 'conjugate', 'denominator',
'from_bytes', 'imag', 'numerator', 'real',
'to_bytes']
Many of the names shown by dir
begin and end with two underscores. These are names for the so-called dunder (double-underscore) or magic methods and magic variables. We'll encounter some of these in the Object Oriented Programming book.
Helpful Hint: Use the sorted
function with the output of dir
:
>>> sorted(dir(range(1)))
['__bool__', '__class__', '__contains__',
'__delattr__', '__dir__', '__doc__', '__eq__',
... a bunch of stuff omitted ...,
'count', 'index', 'start', 'step', 'stop']
Another Helpful Hint: Use pretty print to print the output in an easier to read format:
>>> from pprint import pp
>>> names = sorted(dir(range(1)))
>>> pp(names)
['__bool__',
'__class__',
'__contains__',
'__delattr__',
... a bunch of stuff omitted ...,
'count',
'index',
'start',
'step',
'stop']
Yet Another Helpful Hint: Use a comprehension to limit the output to just the names that don't contain __
.
>>> names = sorted(dir(range(1)))
>>> names = [name for name in names
... if '__' not in name]
>>> print(names)
['count', 'index', 'start', 'step', 'stop']
When used without arguments, the help
function prints some basic help on how to use help
, then leaves you in a special help mode that uses a help>
prompt.
>>> help()
Welcome to Python 3.11's help utility!
If this is your first time using Python, you should definitely check out
the tutorial on the internet at https://docs.python.org/3.11/tutorial/.
Enter the name ...
<omitted text>
help>
From the help>
prompt, you can request help on module names, Python keywords, built-in functions, class names, etc. Type the appropriate words at the help>
prompt and press {Return} or {Enter}.
Quitting the help system may involve two separate steps. If you are currently reading a long help item (such as the help for str
), you may need to press q
to terminate the output. After terminating the output, you may need to type another q
and then press {Return} or {Enter} to exit from the help>
prompt.
As of Python 3.13.0, you no longer need to use write help()
to access the help system. You can, instead, just write help
. When supplying an argument to help
, you still need to use the parentheses.
You can also request help more directly by placing an appropriate identifier between the parentheses:
>>> help(ord)
Help on built-in function ord in module builtins:
ord(c, /)
Return the Unicode code point for a one-character string.
>>> help(bool)
Help on class bool in module builtins:
class bool(int)
| bool(x) -> bool
|
| Returns True when the argument x is true, False otherwise.
| The builtins True and False are the only two instances of the class bool.
| The class bool is a subclass of the class int, and cannot be subclassed.
|
| Method resolution order:
| bool
| int
| object
<omitted text>
>>> help('topics')
Here is a list of available topics. Enter any topic name to get more help.
ASSERTION DELETION LOOPING SHIFTING
ASSIGNMENT DICTIONARIES MAPPINGMETHODS SLICINGS
ATTRIBUTEMETHODS DICTIONARYLITERALS MAPPINGS SPECIALATTRIBUTES
ATTRIBUTES DYNAMICFEATURES METHODS SPECIALIDENTIFIERS
AUGMENTEDASSIGNMENT ELLIPSIS MODULES SPECIALMETHODS
<omitted text>
>>> help('BOOLEAN')
Boolean operations
******************
or_test ::= and_test | or_test "or" and_test
and_test ::= not_test | and_test "and" not_test
not_test ::= comparison | "not" not_test
In the context of Boolean operations, and also when expressions are
used by control flow statements...
<omitted text>
Unless a function is built-in or provided by an imported module, you must create it. A typical function definition (a.k.a., function declaration) looks like this:
def func_name():
func_body
Here, we're assigning the function to func_name
, and func_body
is some code that performs the function's required action(s).
Here's a function named say
that prints Hi!
:
def say():
print('Hi!')
Why do we want a function named say
? To say something, of course! Let's try it. First, we'll create a file named say.py
, then place the following code inside the file.
def say():
print('Output from say')
print('First')
say()
print('Last')
Save the file and run it from the terminal:
$ python say.py
First
Output from say
Last
On line 5 of say.py
, the code say()
is a function call to the say
function. When Python runs this program, it creates a function named say
whose body causes Python to print the text Output from say
when the function executes. However, that doesn't happen immediately.
On line 4, we use print
to display First
on the terminal. On line 5, we call the function say
by appending a pair of parentheses -- ()
-- to the function's name. This causes Python to temporarily jump into the function's body, which prints the text Output from say
. Finally, Python returns to the code that immediately follows the call to say
and prints Last
.
Note that the parentheses on line 5 -- ()
-- make this code a function call. Without the parentheses, say
doesn't do anything useful. It's the function's name, not an invocation. Forgetting the parentheses is usually a bug that can be tough to find since the code may run long after the line with the error. It just won't work. We'll learn about function objects later in the Core Curriculum.
Python programmers often add a triple-quoted string at the beginning of a function's block. This string is a documentation comment -- a docstring -- that Python can access with its help()
function and the __doc__
property. It has no effect on your code unless your program is somehow interested in the comments (which can happen):
def say():
"""
The say function prints "Hi!"
"""
print('Hi!')
print('-' * 60)
print(say.__doc__)
print('-' * 60)
help(say)
------------------------------------------------------------
The say function prints "Hi!"
------------------------------------------------------------
Help on function say in module \_\_main\_\_:
say()
The say function prints "Hi!"
We won't follow this convention at Launch School, but you should learn to recognize it. You will likely encounter it elsewhere.
The scope of an identifier determines where you can use it. Python determines scope by looking at where you initialize the identifier. In Python, identifiers have function scope. That means that anything initialized inside a function is only available within the body of that function and any nested functions. No code outside of the function body can access that identifier.
Consider this code:
def well_howdy(who):
greeting = 'Howdy'
print(f'{greeting}, {who}')
well_howdy('Angie')
print(greeting)
In this code, we have a greeting
variable in the body of well_howdy
. When we call the function, Howdy
is assigned to greeting
, and a more complete greeting message is printed.
When the function is done running, we try to print the value of the greeting
variable. However, instead of seeing Howdy
, we get an error message instead:
NameError: name 'greeting' is not defined
The error occurs since the greeting
variable is only available inside the well_howdy
function. The surrounding code has no way to access the variable.
What happens if we define our own greeting
variable in the outer scope?
greeting = 'Salutations'
def well_howdy(who):
greeting = 'Howdy'
print(f'{greeting}, {who}')
well_howdy('Angie')
print(greeting)
This time, the code prints Salutations
on line 8, thus showing that greeting
is in scope on line 8. However, the outer greeting
variable plays no part in the function's body. The assignment on line 4 hides the greeting
variable from line 1. We call this variable shadowing.
Let's remove the assignment on line 4 and see what happens:
greeting = 'Salutations'
def well_howdy(who):
print(f'{greeting}, {who}')
well_howdy('Angie')
print(greeting)
This time, we get:
Salutations, Angie
Salutations
The key difference here is that we are no longer assigning the greeting
variable in this function. Instead, we're just accessing its current value. It's the assignment in the first example in this section that causes Python to create a new local variable named greeting
. Without any assignments in the function body, Python looks for greeting
in the lexical scope, which means it searches the outer scopes for the nearest definition of greeting
. In this example, the only outer scope of concern is the topmost scope, i.e., the global scope.
If you're familiar with some other programming languages, the following example may be a little surprising:
def scope_test():
if True:
foo = 'Hello'
else:
bar = 'Goodbye'
print(foo)
print(bar)
scope_test()
Hello
UnboundLocalError: cannot access local variable
'bar' where it is not associated with a value
In some languages, the corresponding print code may treat both foo
and bar
as out of scope. Both names may be in scope in other languages, but one will have a default value such as nil
or undefined
. In still other languages, only one name is in scope. Python comes closest to this last approach, but both names are technically in scope. However, since the else
block never runs, bar
is left unassigned. Thus, we get the UnboundLocalError
message when we try to print it.
As you might expect, constants have the same scoping behavior as variables. In fact, so do function, parameter, class, and module names. Method names act a little differently, but that's a topic for another time.
Closely related to the concept of scope is the concept of a namespace. In Python, a namespace is defined as a mapping of identifiers to objects. In essence, namespaces define the scopes in which Python will look for identifiers. When you refer to an identifier, Python first looks for the identifier in the local namespace. That is, it looks in the local scope. If the identifier is not found in the local namespace, Python next looks in any enclosing namespaces, i.e., the outer scopes (but not including the global scope). Next, it searches the global namespace, which is equivalent to the global scope, and, finally, the built-in namespace.
We will not often refer to namespaces at Launch School, but you may encounter the term when looking at other material.
Create a new file named say2.py
with the following code:
print('hello')
print('hi')
print('how do you do')
print('Quite all right')
Go ahead and run it if you want. Notice how we've duplicated the print
call several times. Instead of calling it every time we want to output something, we want to have code we can call when we need to print something.
Now, let's update say2.py
as follows:
def say(text):
print(text)
say('hello')
say('hi')
say('how do you do')
say('Quite all right')
At first glance, this program seems silly. It doesn't reduce the amount of code. In fact, it adds more. Also, the say
function doesn't provide any functionality that print
doesn't already offer. However, there is a benefit here. We've extracted the logic for displaying text in a way that makes our program more flexible. If we need to change the output style, we can change it in one place: the say
function. We don't have to change any other code to get the updated behavior. We'll see such an update in a few minutes.
As we saw earlier, we invoke functions by typing their name and a pair of parentheses. say2.py
illustrates how we define and call a function that takes a value known as an argument. Arguments let you pass data from outside the function's scope into the function so it can access that data. You don't need arguments when the function doesn't need access to outside data.
In say2.py
, the function definition includes (text)
after the function name. This syntax tells us that we should supply (pass) a single argument to the function when we call it. We include an argument between the invocation parentheses to pass it to say
. Thus, say('hello')
passes the argument 'hello'
to the function. We assign the argument's value to the text
parameter inside say
.
The names between parentheses in the function definition are called parameters. You can think of parameters as placeholders for potential arguments, while arguments are the values assigned to those placeholders.
Function names and parameters are both considered variable names in Python. Parameters, in particular, are local variables. They are defined locally within the function's body. A function's name is global or local, depending on whether it is at the program's top level or nested inside a class, module, or another function. We'll see several exercises about these issues below; they are essential even if there isn't much to say about them.
Since parameters are merely placeholders, we typically talk of them as declarations: they are being used to introduce those names as local variables into a function, but don't obviously provide an immediate value until the function is called. That is, they simply declare variable names.
Arguments are objects passed to a function during invocation. Parameters are placeholders for the objects that will be passed to the function when it is invoked.
Many developers conflate the terms "parameter" and "argument" and use them interchangeably. The difference is typically not crucial to understanding, but using correct terminology is a good idea. We expect you to do so on Launch School assessments. Parameters are not arguments. Arguments are not parameters.
You can name functions and parameters with any valid variable name. However, you should use meaningful, explicit names that follow the same naming conventions discussed in the Variables chapter. In the above example, we name the parameter text
since the say
function expects the caller to pass in some text. Other suitable names might be sentence
or message
.
When we call the say
function, we should provide a value -- the argument -- to assign to the text
parameter. For instance, on line 4 of say2.py
, we pass the value 'hello'
to the function, which it uses to initialize text
. The function can use the value in any way it requires by referencing the parameter name. It can even ignore the argument entirely.
Remember: a parameter's scope is the function where the parameter is used. Thus, the scope of the text
parameter in say2.py
is the say
function. You can't reference the parameter outside of the function's body.
You can define functions that take any number of parameters and accept any number of arguments. For instance, the add
function defined below takes two parameters, and we invoke it with 2 arguments:
def add(left, right):
sum = left + right
return sum
sum = add(3, 6)
In the above code, left
and right
are parameters on line 1. On line 2, however, they are local variables that contain the argument values passed to add
on line 5.
Python will raise an error if you pass too many or too few arguments to a function. However, it is possible to bypass this restriction, as we'll see later.
Let's return to say2.py
. When we called say('hello')
, we passed the string 'hello'
as the argument. Thus, 'hello'
was assigned to the text
parameter.
As mentioned earlier, one benefit that functions give us is the ability to make changes in one location. Suppose we want to add a ==>
to the beginning of every line that say
outputs. All we have to do is change one line of code -- we don't have to change the function invocations:
def say(text):
# highlight
print('==> ' + text)
# endhighlight
say('hello') # ==> hello
say('hi') # ==> hi
say('how are you') # ==> how are you
say("I'm fine") # ==> I'm fine
Run this code to see the result. We've now added a ==>
to the beginning of each line. We only had to change a single line of code. Now, you're starting to see the power of functions.
As we've seen in some earlier examples, functions can perform an operation and return a result to the caller for later use. We do that with return values and the return
statement.
All Python function calls evaluate to a value, provided the function doesn't raise an exception (an error). By default, that value is None
; this is the implicit return value of most Python functions. However, when you use a return
statement, you can return a specific value from a function. This is an explicit return value. There is no distinction between implicit and explicit return values outside the function. Still, it's important to remember that all functions return something unless they raise an exception, even if they don't explicitly execute a return
statement.
Let's create an add
function that returns the sum of two numbers:
def add(a, b):
return a + b
add(2, 3) # returns 5
When you run this program, it doesn't print anything since add
doesn't call print
or any other output functions. However, the function does return a value: 5
. When Python encounters the return
statement, it evaluates the expression, terminates the function, then returns the expression's value to the location where we called add
.
Let's capture the return value in a variable and print it to verify that:
def add(a, b):
return a + b
# highlight
two_and_three = add(2, 3)
print(two_and_three) # 5
# endhighlight
Python uses the return
statement to return a value to the code that invoked the function. If you don't specify a value, it returns None
. Either way, the return
statement terminates the function and returns control to the calling function.
Functions that always return a Boolean value, i.e., True
or False
, are called predicates. For example, the following function is a predicate:
def is_digit(char):
if char >= '0' and char <= '9':
return True
return False
You will almost certainly encounter this term in future readings and videos, so commit it to memory.
It's sometimes helpful to invoke a function without some of its arguments. You can do that by providing default values for the function's parameters. Let's update say
to use a default value when the caller omits the argument.
def say(text='hello'):
print(text + '!')
say('Howdy') # Howdy!
say() # hello!
Notice that invoking say
without arguments, as on line 5, prints 'hello!'. We can invoke say
without arguments since we've provided a default value for text
. Nice!
You can provide defaults for any or all of a function's parameters. However, once you specify a default value for a parameter, all subsequent positional parameters must also have default values:
def say(msg1, msg2, msg3='dummy message', msg4):
pass
# SyntaxError: non-default argument follows default argument
It's also worth noting that you can't accept the default value for a parameter and provide an explicit value for a subsequent parameter:
def say(msg1, msg2, msg3='dummy message',
msg4='omitted message'):
print(msg1)
print(msg2)
print(msg3)
print(msg4)
say('a', 'b', 'c', 'd')
# a
# b
# c
# d
say('a', 'b', 'c')
# a
# b
# c
# omitted message
say('a', 'b')
# a
# b
# dummy message
# omitted message
say('a', 'b', , 'd')
# a
# b
# d # oops - expecting 'dummy message'
# omitted message # oops again - expected 'd'
Python has a variety of ways to specify parameters. The easiest is with positional parameters. With positional parameters, the parameter values are taken from the corresponding argument position. Thus, if you have a function that takes 3 parameters, the first parameter is set to the first argument, the second parameter to the second argument, and the third parameter to the third argument. For instance, in the say
function above, the 4 parameters are assigned based on the order of the arguments. Of course, default parameters mean you can omit some arguments.
We'll see some of Python's other parameter types later in the Core Curriculum.
Thus far, all our function calls have used the function_name(obj, ...)
syntax. We call a function by writing parentheses after its name. Any arguments the function needs are provided inside the parentheses.
However, suppose you want to convert a string to all uppercase letters. You might expect to use a function invocation like upper(my_str)
. However, that won't work. You must use a different syntax called method invocation.
Method invocations occur when you prepend an object followed by a period (.
) to a function invocation, e.g., 'xyzzy'.upper()
. We call such function invocations method calls. We also speak of the function as a method. We cover this topic in more detail in the Core Curriculum when we get to Object-Oriented Programming. You can think of the previous code as the function upper
returning a modified version of the string 'xyzzy'
.
Methods provide the same benefits as functions. However, methods work with specific objects. All methods are functions but not vice versa. Every method belongs to a class and requires an object of that class to call it. For instance, in the following example, we're using the string object represented by my_str
to call the upper
method from the str
class:
my_str = 'abc-123-def'
print(my_str.upper()) # ABC-123-DEF
Writing your own methods requires classes, which is how you create custom data types in Python. We're not ready to write our own classes and methods at this point; you'll learn how to do that in our Object Oriented Programming in Python book.
In Python, the distinction between functions and methods may sometimes seem fuzzy. Some function invocations look like method invocations. For instance, consider the math
module from Python's standard libraries. The module provides some mathematical functions that any program can use. Once you import the module, you just call the functions you need:
import math
print(math.sqrt(5)) # 2.23606797749979
Wait. Is that a method call? It sure looks like one. It quacks like one. Since math
references an object, math.sqrt()
must be a method call. However, it is not. While math
is technically a reference to a module
object, we're not using it to perform operations on that object. The sole purpose of the math
object here is to tell Python where to look for those functions.
For brevity, we often speak of functions when discussing functions and methods together. However, you should say methods when discussing functions explicitly designed to require calling objects.
Sometimes, a method mutates the object used to invoke the method: it mutates the caller. In the following example, the pop
method mutates the caller (the list referenced by odd_numbers
):
odd_numbers = [1, 3, 5, 7, 9]
odd_numbers.pop()
print(odd_numbers) # [1, 3, 5, 7]
We can also talk about whether functions mutate their arguments. The following function illustrates this concept:
def add_new_number(my_list):
my_list.append(9)
numbers = [1, 2, 3, 4, 5]
add_new_number(numbers)
print(numbers) # [1, 2, 3, 4, 5, 9]
This code uses the list.append
method to add a new number to the list my_list
.Thus, list.append
mutates its caller. However, the add_new_number
function doesn't mutate a caller (there is no caller). Instead, it mutates its argument.
In general, mutating the caller is acceptable practice; many built-in functions and methods do just that. However, you should avoid mutating arguments since such functions can be tough to debug and is considered poor practice. Almost no built-in functions mutate their arguments.
# Returning a new object
def add_new_number(my_list):
return my_list + [9]
numbers = [1, 2, 3, 4, 5]
new_numbers = add_new_number(numbers)
print(new_numbers) # [1, 2, 3, 4, 5, 9]
print(numbers) # [1, 2, 3, 4, 5]
How do you know which methods mutate the caller and which don't? That's easy when the caller is immutable: the answer is always, "This method does not mutate the caller." However, when the caller is mutable, there's no way to say without inspecting the method's code or checking the documentation.
We discuss object mutation in much more detail in our courses at Launch School.
Functions and methods are fundamental concepts in Python programming. Knowing what a function does is crucial to your development as a Python programmer. You'll use them all the time in programs both big and small.
What happens when you run the following program? Why do we get that result?
def set_foo():
foo = 'bar'
set_foo()
print(foo)
The program outputs an error: NameError: name 'foo' is not defined
Since foo
is created inside a function, it isn't in scope when executing code that isn't part of that function.
Video Walkthrough
Take a look at this code snippet:
foo = 'bar'
def set_foo():
foo = 'qux'
set_foo()
print(foo)
What does this program print? Why?
The program prints bar
since the assignment on line 4 creates a new variable that is local to the function. That is, the foo
variable on line 4 shadows the foo
variable on line 1, so line 4 doesn't change the value of foo
from line 1.
Video Walkthrough
Write a program that uses a multiply
function to multiply two numbers and returns the result. Ask the user to enter the two numbers, then output the numbers and result as a simple equation.
$ python multiply.py
Enter the first number: 3.1416
Enter the second number: 2.7183
3.1416 * 2.7183 = 8.53981128
def multiply(left, right):
return left * right
def get_number(prompt):
entry = input(prompt)
return float(entry)
first_number = get_number('Enter the first number: ')
second_number = get_number('Enter the second number: ')
product = multiply(first_number, second_number)
print(f'{first_number} * {second_number} = {product}')
Video Walkthrough
Consider this code:
def multiply_numbers(num1, num2, num3):
result = num1 * num2 * num3
return result
product = multiply_numbers(2, 3, 4)
Identify the following items in that code:
Item |
---|
function name |
function arguments |
function definition |
function body |
function parameters |
function invocation |
function return value |
all identifiers |
Item | Answer |
---|---|
function name |
multiply_numbers on lines 1 and 5 |
function arguments |
2 , 3 , and 4 between the parentheses on line 5 |
function definition | everything on lines 1-3 |
function body | everything on lines 2 and 3 |
function parameters |
num1 , num2 , and num3 on line 1 |
function invocation |
multiply_numbers(2, 3, 4) on line 5 |
function return value | 24 |
all identifiers |
multiply_numbers , num1 , num2 , num3 , result , and product
|
Video Walkthrough
Errata: At about 1:56 the instructor says that the function name is on lines 3 and 5. In fact, the correct line numbers in the video are lines 3 and 7.
What does the following code print?
def scream(words):
return words + '!!!!'
scream('Yipeee')
This program doesn't output anything. The function returns a value, Yipeee!!!!
, but it doesn't do anything with it. In particular, it doesn't print it.
Video Walkthrough
What does the following code print?
def scream(words):
words = words + '!!!!'
return
print(words)
scream('Yipeee')
This code also doesn't print anything. The return
on line 3 terminates the function before it can print anything.
Video Walkthrough
Without running the following code, what do you think it will do?
def foo(bar, qux):
print(bar)
print(qux)
foo('Hello')
The code will raise an error:
TypeError: foo() missing 1 required positional
argument: 'qux'
We've defined foo
with two parameters. However, we've only passed it one argument. This is an error.
Video Walkthrough
Without running the following code, what do you think it will do?
def foo(bar, qux):
print(bar)
print(qux)
foo(42, 3.141592, 2.718)
The code will raise an error:
TypeError: foo() takes 2 positional arguments,
but 3 were given
We've defined foo
with two parameters. However, we've passed the function three arguments. This is an error.
Video Walkthrough
Without running the following code, what do you think it will do?
def foo(first, second=3, third=2):
print(first)
print(second)
print(third)
foo(42, 3.141592, 2.718)
The code will print the following:
42
3.141592
2.718
Here, we've defined foo
with three parameters, with the second
and third
parameters having default values. However, we've passed all three arguments to the function, so Python ignores the default values.
Video Walkthrough
Without running the following code, what do you think it will do?
def foo(first, second=3, third=2):
print(first)
print(second)
print(third)
foo(42, 3.141592)
The code will print the following:
42
3.141592
2
Here, we've defined foo
with three parameters, with the second
and third
parameters having default values. We've passed the function two arguments, so Python uses the default value for the last argument.
Video Walkthrough
Without running the following code, what do you think it will do?
def foo(first, second=3, third=2):
print(first)
print(second)
print(third)
foo(42)
The code will print the following:
42
3
2
Here, we've defined foo
with three parameters, with the second
and third
parameters having a default value. We've passed the function one argument, so Python uses the default value for the last two parameters.
Video Walkthrough
Without running the following code, what do you think it will do?
def foo(first, second=3, third=2):
print(first)
print(second)
print(third)
foo()
The code will raise an error:
TypeError: foo() missing 1 required positional
argument: 'first'
Here, we've defined foo
with three parameters, with the second
and third
parameters having default values. We haven't passed the function any arguments. That's an error, though - the first
parameter has no default value.
Video Walkthrough
Without running the following code, what do you think it will do?
def foo(first, second=3, third):
print(first)
print(second)
print(third)
foo(42)
The code will raise an error:
SyntaxError: non-default argument follows
default argument
Here, we've defined foo
with three parameters, with the second
parameter having a default value. This is an error, however. Once Python sees a positional parameter with a default value, all subsequent parameters must have default values.
Video Walkthrough
Identify all of the identifiers on each line of the following code.
def multiply(left, right):
return left * right
def get_num(prompt):
return float(input(prompt))
first_number = get_num('Enter the first number: ')
second_number = get_num('Enter the second number: ')
product = multiply(first_number, second_number)
print(f'{first_number} * {second_number} = {product}')
The identifiers in this code are multiply
, left
, right
, first_number
, second_number
, get_num
, prompt
, float
, input
, product
, and print
. The following table shows where each identifier appears in the code.
Identifier | Appears on these lines |
---|---|
multiply |
1, 9 |
left |
1, 2 |
right |
1, 2 |
first_number |
7, 9, 10 |
second_number |
8, 9, 10 |
get_num |
4, 7, 8 |
prompt |
4, 5 |
float |
5 |
input |
5 |
product |
9, 10 |
print |
10 |
Video Walkthrough
Using the code below, classify the identifiers as global, local, or built-in. For our purposes, you may assume this code is the entire program.
def multiply(left, right):
return left * right
def get_num(prompt):
return float(input(prompt))
first_number = get_num('Enter the first number: ')
second_number = get_num('Enter the second number: ')
product = multiply(first_number, second_number)
print(f'{first_number} * {second_number} = {product}')
Category | Identifiers |
---|---|
Global |
multiply , get_num , first_number , second_number , product
|
Local |
left , right , prompt
|
Built-in |
float , input , print
|
Functions defined in a program file are global identifiers unless those functions are defined as an object property or nested inside another function. Thus, multiply
, get_num
, and product
are globals in this program. Function parameters and variables initialized inside a function are always local identifiers. Thus, left
, right
, and prompt
are all local variables. first_number
and second_number
are initialized as global variables on lines 7 and 8 respectively, then used on lines 9 and 10.
Video Walkthrough
In the code shown below, identify all of the function names and parameters present in the code. Include the line numbers for each item identified.
def multiply(left, right):
return left * right
def get_num(prompt):
return float(input(prompt))
first_number = get_num('Enter the first number: ')
second_number = get_num('Enter the second number: ')
product = multiply(first_number, second_number)
print(f'{first_number} * {second_number} = {product}')
multiply
: defined on line 1, used on line 9.
get_num
: defined on line 4, used on lines 7 and 8.
float
: built-in function used on line 5.
input
: built-in function used on line 5.
print
: built-in function used on line 10.
left
and right
are defined on line 1 and then used on line 2.
prompt
is defined on line 4 and then used on line 5.
Note that left
and right
are defined as parameters for the multiply
function on line 1. We reference those parameters on line 2, but usually speak of them as local variables instead of parameters. For this exercise, it's okay if you said that left
and right
are present on line 2. It's also okay if you omitted it.
Likewise, prompt
is defined as a parameter for the get_num
function on line 4, but used as a local variable on line 5.
Video Walkthrough
Which of the identifiers in the following program are function names? Which are method names? Which are built-in functions?
def say(message):
print(f'==> {message}')
string1 = input()
string2 = input()
say(max(string1.upper(), string2.lower()))
Function Names | Method Names | Built-in Function Names |
---|---|---|
say |
||
print |
||
input |
||
max |
||
upper |
||
lower |
If you identified all the method and built-in function names as function names, that's an acceptable answer as well: all methods are functions, and built-in functions are just functions that are, well, built-in.
Video Walkthrough
The following function returns a list of the remainders of dividing the numbers in numbers
by 3:
def remainders_3(numbers):
return [number % 3 for number in numbers]
Use this function to determine which of the following lists contains at least one number that is not evenly divisible by 3 (that is, the remainder is not 0
):
numbers_1 = [0, 1, 2, 3, 4, 5, 6]
numbers_2 = [1, 2, 4, 5]
numbers_3 = [0, 3, 6]
numbers_4 = []
Note: when working with integers, a value of 0
is "falsy"; all other integers are "truthy".
print(any(remainders_3(numbers_1))) # True
print(any(remainders_3(numbers_2))) # True
print(any(remainders_3(numbers_3))) # False
print(any(remainders_3(numbers_4))) # False
remainders_3
returns a list of integers between 0 and 2, inclusive. A value of 0
means the corresponding number is divisible by 3, while a value of 1
or 2
means the number is not divisible by 3. Since 0
is falsy and 1
and 2
are truthy, we can use any
to determine whether any of the numbers are non-zero.
Video Walkthrough
The following function returns a list of the remainders of dividing the numbers in numbers
by 5:
def remainders_5(numbers):
return [number % 5 for number in numbers]
Use this function to determine which of the following lists do not contain any numbers that are divisible by 5:
numbers_1 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
numbers_2 = [1, 2, 3, 4, 6, 7, 8, 9]
numbers_3 = [0, 5, 10]
numbers_4 = []
Note: when working with integers, a value of 0
is "falsy"; all other integers are "truthy".
print(all(remainders_5(numbers_1))) # False
print(all(remainders_5(numbers_2))) # True
print(all(remainders_5(numbers_3))) # False
print(all(remainders_5(numbers_4))) # True
remainders_5
is similar to remainders_3
in the previous exercise in that it returns a list of integers. In this list, a value of 0
means the corresponding number is divisible by 5, while a non-zero value means the number is not divisible by 5. Since 0
is falsy and the other integers are truthy, we can use all
to determine whether all of the numbers are not divisible by 5
.
Video Walkthrough