Whether you’re calculating salaries, bank interest, or cellular frequencies, it’s hard to imagine a program that doesn’t use numbers in one way or another. Python has three different numeric types: int
, float
, and complex
. For most of us, it’s enough to know about (and work with) int
(for whole numbers) and float
(for numbers with a fractional component).
Numbers are not only fundamental to programming, but also give us a good introduction to how a programming language operates. Understanding how variable assignment and function arguments work with integers and floats will help you to reason about more complex types, such as strings, tuples, and dicts.
This chapter contains exercises that work with numbers, as inputs and as outputs. Although working with numbers can be fairly basic and straightforward, converting between them, and integrating them with other data types, can sometimes take time to get used to.
Module for generating random numbers and selecting random elements |
|||
|
|||
Returns an iterator with the reversed elements of an iterable |
This first exercise is designed to get your fingers warmed up for the rest of the book. It also introduces a number of topics that will repeat themselves over your Python career: loops, user input, converting types, and comparing values.
More specifically, programs all have to get input to do something interesting, and that input often comes from the user. Knowing how to ask the user for input not only is useful, but allows us to think about the type of data we’re getting, how to convert it into a format we can use, and what the format would be.
As you might know, Python only provides two kinds of loops: for
and while
. Knowing how to write and use them will serve you well throughout your Python career. The fact that nearly every type of data knows how to work inside of a for
loop makes such loops common and useful. If you’re working with database records, elements in an XML file, or the results from searching for text using regular expressions, you’ll be using for
loops quite a bit.
When run, the function chooses a random integer between 0 and 100 (inclusive).
Each time the user enters a guess, the program indicates one of the following:
If the user guesses correctly, the program exits. Otherwise, the user is asked to try again.
We’ll use the randint
(http://mng.bz/mBEn) function in the random
module to generate a random number. Thus, you can say
import random number = random.randint(10, 30)
and number
will contain an integer from 10 to (and including) 30. We can then do whatever we want with number
--print it, store it, pass it to a function, or use it in a calculation.
We’ll also be prompting the user to enter text with the input
function. We’ll actually be using input
quite a bit in this book to ask the user to tell us something. The function takes a single string as an argument, which is displayed to the user. The function then returns the string containing whatever the user entered; for example:
name = input('Enter your name: ') print(f'Hello, {name}!')
Note If the user simply presses Enter when presented with the input
prompt, the value returned by input
is an empty string, not None
. Indeed, the return value from input
will always be a string, regardless of what the user entered.
Note In Python 2, you would ask the user for input using the raw_input
function. Python 2’s input
function was considered dangerous, since it would ask the user for input and then evaluate the resulting string using the eval
function. (If you’re interested, see http://mng.bz/6QGG.) In Python 3, the dangerous function has gone away, and the safe one has been renamed input
.
At its heart, this program is a simple application of the comparison operators (==
, <
, and >
) to a number, such that a user can guess the random integer that the computer has chosen. However, several aspects of this program merit discussion.
First and foremost, we use the random
module to generate a random number. After importing random
, we can then invoke random.randint
, which takes two parameters, returning a random integer. In general, the random
module is a useful tool whenever you need to choose a random value.
Note that the maximum number in random.randint
is inclusive. This is unusual in Python; most of the time, such ranges in Python are exclusive, meaning that the higher number is not included.
Tip The random
module doesn’t just generate random numbers. It also has functions to choose one or more elements from a Python sequence.
Now that the computer has chosen a number, it’s the user’s turn to guess what that number is. Here, we start an infinite loop in Python, which is most easily created with while
True
. Of course, it’s important that there be a way to break out of the loop; in this case, it will be when the user correctly guesses the value of answer
. When that happens, the break
command is used to exit from the innermost loop.
The input
(http://mng.bz/wB27) function always returns a string. This means that if we want to guess a number, we must turn the user’s input string into an integer. This is done in the same way as all conversions in Python: by using the target type as a function, passing the source value as a parameter. Thus int('5')
will return the integer 5, whereas str(5)
will return the string '5'
. You can also create new instances of more complex types by invoking the class as a function, as in list('abc')
or dict([('a',
1),
('b',
2),
('c',
3)])
.
In Python 3, you can’t use <
and >
to compare different types. If you neglect to turn the user’s input into an integer, the program will exit with an error, saying that it can’t compare a string (i.e., the user’s input) with an integer.
Note In Python 2, it wasn’t an error to compare objects of different types. But the results you would get were a bit surprising, if you didn’t know what to expect. That’s because Python would first compare them by type, and then compare them within that type. In other words, all integers were smaller than all lists, and all lists were smaller than all strings. Why would you ever want to use <
and >
on objects of different types? You probably wouldn’t, and I found that this functionality confused people more than it helped them. In Python 3, you can’t make such a comparison; trying to check with 1
<
[10,
20,
30]
will result in a TypeError
exception.
In this exercise, and the rest of this book, I use f-strings to insert values from variables into our strings. I’m a big fan of f-strings and encourage you to try them as well. (See the sidebar discussing f-strings later in this chapter.)
import random def guessing_game(): answer = random.randint(0, 100) while True: user_guess = int(input('What is your guess? ')) if user_guess == answer: print(f'Right! The answer is {user_guess}') break if user_guess < answer: print(f'Your guess of {user_guess} is too low!') else: print(f'Your guess of {user_guess} is too high!') guessing_game()
You can work through a version of this code in the Python Tutor at http://mng.bz/ vx1q.
Note We’re going to assume, for the purposes of this exercise, that our user will only enter valid data, namely integers. Remember that the int
function normally assumes that we’re giving it a decimal number, which means that its argument may contain only digits. If you really want to be pedantic, you can use the str.isdigit
method (http://mng.bz/oPVN) to check that a string contains only digits. Or you can trap the ValueError
exception you’ll get if you run int
on something that can’t be turned into an integer.
Watch this short video walkthrough of the solution: https://livebook.manning.com/ video/python-workout.
You’ll often be getting input from users, and because it comes as a string, you’ll often need to convert it into other types, such as (in this exercise) integers. Here are some additional ideas for ways to practice this idea:
Modify this program, such that it gives the user only three chances to guess the correct number. If they try three times without success, the program tells them that they didn’t guess in time and then exits.
Not only should you choose a random number, but you should also choose a random number base, from 2 to 16, in which the user should submit their input. If the user inputs “10” as their guess, you’ll need to interpret it in the correct number base; “10” might mean 10 (decimal), or 2 (binary), or 16 (hexadecimal).
Try the same thing, but have the program choose a random word from the dictionary, and then ask the user to guess the word. (You might want to limit yourself to words containing two to five letters, to avoid making it too horribly difficult.) Instead of telling the user that they should guess a smaller or larger number, have them choose an earlier or later word in the dict.
One of my favorite types of exercises involves reimplementing functionality that we’ve seen elsewhere, either inside of Python or in Unix. That’s the background for this next exercise, in which you’ll reimplement the sum
(http://mng.bz/MdW2) function that comes with Python. That function takes a sequence of numbers and returns the sum of those numbers. So if you were to invoke sum([1,2,3])
, the result would be 6
.
The challenge here is to write a mysum
function that does the same thing as the built-in sum
function. However, instead of taking a single sequence as a parameter, it should take a variable number of arguments. Thus, although you might invoke sum([1,2,3])
, you’d instead invoke mysum(1,2,3)
or mysum(10,20,30,40,50)
.
Note The built-in sum
function takes an optional second argument, which we’re ignoring here.
And no, you shouldn’t use the built-in sum
function to accomplish this! (You’d be amazed just how often someone asks me this question when I’m teaching courses.)
This exercise is meant to help you think about not only numbers, but also the design of functions. And in particular, you should think about the types of parameters functions can take in Python. In many languages, you can define functions multiple times, each with a different type signature (i.e., number of parameters, and parameter types). In Python, only one function definition (i.e., the last time that the function was defined) sticks. The flexibility comes from appropriate use of the different parameter types.
Tip If you’re not familiar with it, you’ll probably want to look into the splat operator (asterisk), described in this Python tutorial: http://mng.bz/aR4J.
The mysum
function is a simple example of how we can use Python’s “splat” operator (aka *
) to allow a function to receive any number of arguments. Because we have prefaced the name numbers
with *
, we’re telling Python that this parameter should receive all of the arguments, and that numbers
will always be a tuple.
Even if no arguments are passed to our function, numbers
will still be a tuple. It’ll be an empty tuple, but a tuple nonetheless.
The splat operator is especially useful when you want to receive an unknown number of arguments. Typically, you’ll expect that all of the arguments will be of the same type, although Python doesn’t enforce such a rule. In my experience, you’ll then take the tuple (numbers
, in this case) and iterate over each element with either a for
loop or a list comprehension.
Note If you’re retrieving elements from *args
with numeric indexes, then you’re probably doing something wrong. Use individual, named parameters if you want to pick them off one at a time.
Because we expect all of the arguments to be numeric, we set our output
local variable to 0 at the start of the function, and then we add each of the individual numbers to it in a for
loop. Once we have this function, we can invoke it whenever we want, on any list, set, or tuple of numbers.
While you might not use sum
(or reimplement it) very often, *args
is an extremely common way for a function to accept an unknown number of arguments.
def mysum(*numbers): output = 0 for number in numbers: output += number return output print(mysum(10, 20, 30, 40))
You can work through this code in the Python Tutor at http://mng.bz/nPQg.
Watch this short video walkthrough of the solution: https://livebook.manning.com/ video/python-workout.
It’s extremely common to iterate over the elements of a list or tuple, performing an operation on each element and then (for example) summing them. Here are some examples:
The built-in version of sum
takes an optional second argument, which is used as the starting point for the summing. (That’s why it takes a list of numbers as its first argument, unlike our mysum
implementation.) So sum([1,2,3],
4)
returns 10, because 1+2+3 is 6, which would be added to the starting value of 4. Reimplement your mysum
function such that it works in this way. If a second argument is not provided, then it should default to 0. Note that while you can write a function in Python 3 that defines a parameter after *args
, I’d suggest avoiding it and just taking two arguments--a list and an optional starting point.
Write a function that takes a list of numbers. It should return the average (i.e., arithmetic mean) of those numbers.
Write a function that takes a list of words (strings). It should return a tuple containing three integers, representing the length of the shortest word, the length of the longest word, and the average word length.
Write a function that takes a list of Python objects. Sum the objects that either are integers or can be turned into integers, ignoring the others.
System administrators often use Python to perform a variety of tasks, including producing reports from user inputs and files. It’s not unusual to report how often a particular error message has occurred, or which IP addresses have accessed a server most recently, or which usernames are most likely to have incorrect passwords. Learning how to accumulate information over time and produce some basic reports (including average times) is thus useful and important. Moreover, knowing how to work with floating-point values, and the differences between them and integers, is important.
For this exercise, then, we’ll assume that you run 10 km each day as part of your exercise regime. You want to know how long, on average, that run takes.
Write a function (run_timing
) that asks how long it took for you to run 10 km. The function continues to ask how long (in minutes) it took for additional runs, until the user presses Enter. At that point, the function exits--but only after calculating and displaying the average time that the 10 km runs took.
For example, here’s what the output would look like if the user entered three data points:
Enter 10 km run time: 15 Enter 10 km run time: 20 Enter 10 km run time: 10 Enter 10 km run time: <enter> Average of 15.0, over 3 runs
Note that the numeric inputs and outputs should all be floating-point values. This exercise is meant to help you practice converting inputs into appropriate types, along with tracking information over time. You’ll probably be tracking data that’s more sophisticated than running times and distances, but the idea of accumulating data over time is common in programs, and it’s important to see how to do this in Python.
In the previous exercise, we saw that input
is a function that returns a string, based on input from the user. In this case, however, the user might provide two types of input; they might enter a number, but they also might enter the empty string.
Because empty strings, as well as the numeric 0, are considered to be False
within an if
statement, it’s common for Python programs to use an expression as shown in the solution:
if not one_run: break
It’s unusual, and would be a bit weird, to say
if len(one_run) == 0: break
Although this works, it’s not considered good Python style, according to generally accepted conventions. Following these conventions can make your code more Pythonic, and thus more readable by other developers. In this case, using not
in front of a variable that might be empty, and thus providing us with a False
value in this context, is much more common.
In a real-world Python application, if you’re taking input from the user and calling float
(http://mng.bz/gyYR), you should probably wrap it within try
(http://mng .bz/5aY1), in case the user gives you an illegal value:
try: n = float(input('Enter a number: ')) print(f'n = {n}') except ValueError as e: print('Hey! That's not a valid number!')
Also remember that floating-point numbers are not completely accurate. They’re good enough for measuring the time it takes to run, but they’re a bad idea for any sensitive measurement, such as a scientific or financial calculation.
If you didn’t know this already, then I suggest you go to your local interactive Python interpreter and ask it for the value of 0.1
+
0.2
. You might be surprised by the results. (You can also go to http://mng.bz/6QGD and see how this works in other programming languages.)
One common solution for this problem is to use integers. Instead of keeping track of dollars and cents (as a float
), you can just keep track of cents (as an int
).
def run_timing(): """Asks the user repeatedly for numeric input. Prints the average time and number of runs.""" number_of_runs = 0 total_time = 0 while True: ❶ one_run = input('Enter 10 km run time: ') if not one_run: ❷ break number_of_runs += 1 total_time += float(one_run) average_time = total_time / number_of_runs print(f'Average of {average_time}, over {number_of_runs} runs') run_timing()
❶ Look, it’s an infinite loop! It might seem weird to have “while True,” and it’s a very bad idea to have such a loop without any “break” statement to exit when a condition is reached. But as a general way of getting an unknown number of inputs from the users, I think it’s totally fine.
❷ If one_run is an empty string, stop.
You can work through this code in the Python Tutor at http://mng.bz/4A1g.
Watch this short video walkthrough of the solution: https://livebook.manning.com/ video/python-workout.
Floating-point numbers are both necessary and potentially dangerous in the programming world; necessary because many things can only be represented with fractional numbers, but potentially dangerous because they aren’t exact. You should thus think about when and where you use them. Here are two exercises in which you’ll want to use float
:
Write a function that takes a float
and two integers (before
and after
). The function should return a float
consisting of before
digits before the decimal point and after
digits after. Thus, if we call the function with 1234.5678
, 2
and 3
, the return value should be 34.567
.
Explore the Decimal
class (http://mng.bz/oPVr), which has an alternative floating-point representation that’s as accurate as any decimal number can be. Write a function that takes two strings from the user, turns them into decimal
instances, and then prints the floating-point sum of the user’s two inputs. In other words, make it possible for the user to enter 0.1
and 0.2
, and for us to get 0.3
back.
Loops are everywhere in Python, and the fact that most built-in data structures are iterable makes it easy to work through them, one element at a time. However, we typically iterate over an object forward, from its first element to the last one. Moreover, Python doesn’t automatically provide us with the indexes of the elements. In this exercise, you’ll see how a bit of creativity, along with the built-in reversed
and enumerate
functions, can help you to get around these issues.
Hexadecimal numbers are fairly common in the world of computers. Actually, that’s not entirely true; some programmers use them all of the time. Other programmers, typically using high-level languages and doing things such as web development, barely even remember how to use them.
Now, the fact is that I barely use hexadecimal numbers in my day-to-day work. And even if I were to need them, I could use Python’s built-in hex
function (http://mng .bz/nPxg) and 0x
prefix. The former takes an integer and returns a hex string; the latter allows me to enter a number using hexadecimal notation, which can be more convenient. Thus, 0x50
is 80, and hex(80)
will return the string 0x50
.
For this exercise, you need to write a function (hex_output
) that takes a hex number and returns the decimal equivalent. That is, if the user enters 50
, you’ll assume that it’s a hex number (equal to 0x50
) and will print the value 80
to the screen. And no, you shouldn’t convert the number all at once using the int
function, although it’s permissible to use int
one digit at a time.
This exercise isn’t meant to test your math skills; not only can you get the hex equivalent of integers with the hex
function, but most people don’t even need that in their day-to-day lives. However, this does touch on the conversion (in various ways) across types that we can do in Python, thanks to the fact that sequences (e.g., strings) are iterable. Consider also the built-in functions that you can use to solve this problem even more easily than if you had to write things from scratch.
Tip Python’s exponentiation operator is **
. So the result of 2**3
is the integer 8
.
A key aspect of Python strings is that they are sequences of characters, over which we can iterate in a for
(http://mng.bz/vxOJ) loop. However, for
loops in Python, unlike their C counterparts, don’t give us (or even use) the characters’ indexes. Rather, they iterate over the characters themselves.
If we want the numeric index of each character, we can use the built-in enumerate
(http://mng.bz/qM1K) function. This function returns a two-element tuple with each iteration; using Python’s multiple-assignment (“unpacking”) syntax, we can capture each of these values and stick them into our power
and digit
variables.
Here’s an example of how we can use enumerate
to print the first four letters of the alphabet, along with the letters’ indexes in the string:
for index, one_letter in enumerate('abcd'): print(f'{index}: {one_letter}')
Note Why does Python have enumerate
at all? Because in many other languages, such as C, for
loops iterate over sequences of numbers, which are used to retrieve elements from a sequence. But in Python, our for
loops retrieve the items directly, without needing any explicit index variable. enumerate
thus produces the indexes based on the elements--precisely the opposite of how things work in other languages.
You also see the use of reversed
(http://mng.bz/7XYx) here, such that we start with the final digit and work our way up to the first digit. reversed
is a built-in function that returns a new string whose value is the reverse of the old one. We could get the same result using slice syntax, hexnum[::-1]
, but I find that many people are confused by this syntax. Also, the slice returns a new string, whereas reversed
returns an iterator, which consumes less memory.
We need to convert each digit of our decimal number, which was entered as a string, into an integer. We do that with the built-in int
(http://mng.bz/4Ava) function, which we can think of as creating a new instance of the int
class or type. We also see that int
takes two arguments. The first is mandatory and is the string we want to turn into an integer. The second is optional and contains the number base. Since we’re converting from hexadecimal (i.e., base 16), we pass 16
as the second argument.
def hex_output(): decnum = 0 hexnum = input('Enter a hex number to convert: ') for power, digit in enumerate(reversed(hexnum)): ❶ decnum += int(digit, 16) * (16 ** power) ❷ print(decnum) hex_output()
❶ reversed returns a new iterable, which returns another iterable’s elements in reverse order. By invoking enumerate on the output from reversed, we get each element of hexnum, one at a time, along with its index, starting with 0.
❷ Python’s ** operator is used for exponentiation.
You can work through this code in the Python Tutor at http://mng.bz/Qy8e.
Watch this short video walkthrough of the solution: https://livebook.manning.com/ video/python-workout.
Every Python developer should have a good understanding of the iterator protocol, which for
loops and many functions use. Combining for
loops with other objects, such as enumerate
and slices, can help to make your code shorter and more maintainable.
Reimplement the solution for this exercise such that it doesn’t use the int
function at all, but rather uses the built-in ord
and chr
functions to identify the character. This implementation should be more robust, ignoring characters that aren’t legal for the entered number base.
Write a program that asks the user for their name and then produces a “name triangle”: the first letter of their name, then the first two letters, then the first three, and so forth, until the entire name is written on the final line.
It’s hard to imagine a Python program that doesn’t use numbers. Whether as numeric indexes (into a string, list, or tuple), counting the number of times an IP address appears in a log file, or calculating interest rates on bank loans, you’ll be using numbers all of the time.
Remember that Python is strongly typed, meaning that integers and strings (for example) are different types. You can turn strings into integers with int
, and integers into strings with str
. And you can turn either of these types into a floating-point number with float
.
In this chapter, we saw a few ways we can work with numbers of different types. You’re unlikely to write programs that only use numbers in this way, but feeling confident about how they work and fit into the larger Python ecosystem is important.