Please don’t anthropomorphize computers; they find it very annoying. When a computer presents you with an error message, it’s not because you’ve offended it. Computers are the most sophisticated tools most of us will ever interact with, but still, they’re just tools.
Even so, it’s easy to blame these tools. Because much of learning to program is self-directed, it’s common to feel like a failure when you still need to consult the internet multiple times a day, even though you’ve been studying Python for months. But even professional software developers search the internet or consult documentation to answer their programming questions.
Unless you have the financial or social resources to hire a private tutor who can answer your programming questions, you’re stuck with your computer, internet search engines, and your own fortitude. Fortunately, your questions have almost certainly been asked before. As a programmer, being able to find answers on your own is far more important than any algorithms or data structure knowledge. This chapter guides you through developing this crucial skill.
When they’re confronted with an error message’s large wall of technobabble text, many programmers’ first impulse is to completely ignore it. But inside this error message is the answer to what’s wrong with your program. Finding this answer is a two-step process: examining the traceback and doing an internet search of the error message.
Python programs crash when the code raises an exception that an except
statement doesn’t handle. When this happens, Python displays the exception’s message and a traceback. Also called a stack trace, the traceback shows the place in your program where the exception happened and the trail of function calls that led up to it.
To practice reading tracebacks, enter the following buggy program and save it as abcTraceback.py. The line numbers are for reference only and aren’t part of the program.
1. def a():
2. print('Start of a()')
1 3. b() # Call b().
4.
5. def b():
6. print('Start of b()')
2 7. c() # Call c().
8.
9. def c():
10. print('Start of c()')
3 11. 42 / 0 # This will cause a zero divide error.
12.
13. a() # Call a().
In this program, the a()
function calls b()
1, which calls c()
2. Inside c()
, the 42 / 0
expression 3 causes a zero divide error. When you run this program, the output should look like this:
Start of a()
Start of b()
Start of c()
Traceback (most recent call last):
File "abcTraceback.py", line 13, in <module>
a() # Call a().
File "abcTraceback.py", line 3, in a
b() # Call b().
File "abcTraceback.py", line 7, in b
c() # Call c().
File "abcTraceback.py", line 11, in c
42 / 0 # This will cause a zero divide error.
ZeroDivisionError: division by zero
Let’s examine this traceback line by line, starting with this line:
Traceback (most recent call last):
This message lets you know that what follows is a traceback. The most recent call last
text indicates that each of the function calls is listed in order, starting with the first function call and ending with the most recent.
The next line shows the traceback’s first function call:
File "abcTraceback.py", line 13, in <module>
a() # Call a().
These two lines are the frame summary, and they show the information inside a frame object. When a function is called, the local variable data as well as where in the code to return to after the function call ends are stored in a frame object. Frame objects hold local variables and other data associated with function calls. Frame objects are created when the function is called and destroyed when the function returns. The traceback shows a frame summary for each frame leading up to the crash. We can see that this function call is on line 13 of abcTraceback.py, and the <module>
text tells us this line is in the global scope. Line 13 is displayed with two spaces of indentation next.
The four lines that follow are the next two frame summaries:
File "abcTraceback.py", line 3, in a
b() # Call b().
File "abcTraceback.py", line 7, in b
c() # Call c().
We can tell from the line 3, in a
text that b()
was called on line 3 inside the a()
function, which led to c()
being called on line 7 inside the b()
function. Notice that the print()
calls on lines 2, 6, and 10 aren’t displayed in the traceback, even though they ran before the function calls occurred. Only the lines containing function calls that lead up to the exception are displayed in the traceback.
The last frame summary shows the line that caused the unhandled exception, followed by the name of the exception and the exception’s message:
File "abcTraceback.py", line 11, in c
42 / 0 # This will cause a zero divide error.
ZeroDivisionError: division by zero
Note that the line number given by the traceback is where Python finally detected an error. The true source of the bug could be somewhere before this line.
Error messages are notoriously short and inscrutable: the three words division by zero
won’t mean anything to you unless you know that dividing a number by zero is mathematically impossible and a common software bug. In this program, the bug isn’t too hard to find. Looking at the line of code in the frame summary, it’s clear where in the 42 / 0
code the zero divide error is happening.
But let’s look at a more difficult case. Enter the following code into a text editor and save it as zeroDivideTraceback.py:
def spam(number1, number2):
return number1 / (number2 - 42)
spam(101, 42)
When you run this program, the output should look like this:
Traceback (most recent call last):
File "zeroDivideTraceback.py", line 4, in <module>
spam(101, 42)
File "zeroDivideTraceback.py", line 2, in spam
return number1 / (number2 - 42)
ZeroDivisionError: division by zero
The error message is the same, but the zero divide in return number1 / (number2 - 42)
isn’t quite so obvious. You can deduce that there is a division happening from the /
operator, and that the expression (number2 - 42)
must evaluate to 0
. This would lead you to conclude that the spam()
function fails whenever the number2
parameter is set to 42
.
Sometimes the traceback might indicate that an error is on the line after the true cause of the bug. For example, in the following program, the first line is missing the closing parenthesis:
print('Hello.'
print('How are you?')
But the error message for this program indicates the problem is on the second line:
File "example.py", line 2
print('How are you?')
^
SyntaxError: invalid syntax
The reason is that the Python interpreter didn’t notice the syntax error until it read the second line. The traceback can indicate where things went wrong, but that isn’t always the same as where the actual cause of a bug is. If the frame summary doesn’t give you enough information to figure out the bug, or if the true cause of the bug is on a previous line not shown by the traceback, you’ll have to step through the program with a debugger or check any logging messages to find the cause. This can take a significant amount of time. An internet search of the error message might give you critical clues about the solution much more quickly.
Often, error messages are so short they’re not even full sentences. Because programmers encounter them regularly, they’re intended as reminders rather than full explanations. If you’re encountering an error message for the first time, copying and pasting it into an internet search frequently returns a detailed explanation of what the error means and what its likely causes are. Figure 1-1 shows the results of a search for python “ZeroDivisionError: division by zero”. Including quotation marks around the error message helps find the exact phrase, and adding the word python can narrow down your search as well.
Searching for error messages isn’t cheating. Nobody can be expected to memorize every possible error message for a programming language. Professional software developers search the internet for programming answers on a daily basis.
You might want to exclude any part of the error message that is particular to your code. For example, consider the following error messages:
>>> print(employeRecord)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
1 NameError: name 'employeRecord' is not defined
>>> 42 - 'hello'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
2 TypeError: unsupported operand type(s) for -: 'int' and 'str'
This example has a typo in the variable employeRecord
, causing an error 1. Because the identifier employeRecord
in NameError: name 'employeRecord' is not defined
is specific to your code, you might want to instead search for python “NameError: name” “is not defined”. In the last line, the 'int' and 'str'
part of the error message 2 seems to refer to the 42
and 'hello'
values, so truncating the search to python “TypeError: unsupported operand type(s) for” would avoid including parts particular to your code. If these searches don’t yield useful results, try including the full error message.
The best way to fix mistakes is to not make them in the first place. Lint software, or linters, are applications that analyze your source code to warn you of any potential errors. The name references the small fibers and debris collected by a clothes dryer’s lint trap. Although a linter won’t catch all errors, static analysis (examining source code without running it) can identify common errors caused by typos. (Chapter 11 explores how to use type hints for static analysis.) Many text editors and integrated development environments (IDEs) incorporate a linter that runs in the background and can point out problems in real time, such as in Figure 1-2.
The near-instant notifications that a linter provides greatly improves your programming productivity. Without one, you’d have to run your program, watch it crash, read the traceback, and then find the line in your source code to fix a typo. And if you’ve made multiple typos, this run-fix cycle would only find them one at a time. Linting can point out multiple errors at once, and it does so directly in the editor, so you can see the line on which they occur.
Your editor or IDE might not come with a lint feature, but if it supports plug-ins, almost certainly a linter will be available. Often, these plug-ins use a linting module called Pyflakes or some other module to do their analysis. You can install Pyflakes from https://pypi.org/project/pyflakes/ or by running pip install --user pyflakes
. It’s well worth the effort.
IDLE, the IDE that comes with Python, doesn’t have a linter or the capability of having one installed.
When internet searches and linters fail to solve your problem, you can ask for programming help on the internet. But there is an etiquette to efficiently asking for advice. If experienced software developers are willing to answer your questions at no charge, it’s best to make efficient use of their time.
Asking strangers for programming help should always be a last resort. Hours or days could pass before someone replies to your posted question, if you get a reply at all. It’s much faster to search the web for other people who have already asked your question and read their answers. Online documentation and search engines were made to relieve the question-answering work that would otherwise have to be done by humans.
But when you’ve exhausted your options and must ask a human audience your programming question, avoid the following common mistakes:
This list of “don’ts” isn’t just for decorum; these habits prevent your helpers from helping you. Your helper’s first step will be to run your code and try to reproduce your problem. To do so, they’ll need a lot of information about your code, computer, and intentions. It’s far more common to provide too little information than too much. The next several sections explore what you can do to prevent these common mistakes. I’ll assume that you’re posting your question to an online forum, but these guidelines also apply to cases when you’re emailing questions to a single person or mailing list.
If you approached someone in person, asking “Can I ask you a question?” would be a short, pleasant means to see if your helper was available. But on online forums, your helper can hold off on a reply until they have the time to do so. Because there could be hours between replies, it’s best to supply all the information your helper might need in your initial post instead of asking for permission to ask your question. If they don’t reply, you can copy and paste this information to a different forum.
It’s easy to assume that your helpers know what you’re talking about when you explain your problem. But programming is an expansive field, and they might not have experience with the particular area in which you’re having trouble. So it’s important to state your question in the form of an actual question. Although sentences that begin with “I want to . . .” or “The code isn’t working” can imply what your question is, be sure to include explicit questions: literally, sentences that end with a question mark. Otherwise, it’s probably unclear what you’re asking.
Asking a Python question on a JavaScript forum or an algorithms question on a network security mailing list will likely be unproductive. Often, mailing lists and online forums have Frequently Asked Questions (FAQ) documents or description pages that explain which topics are appropriate to discuss. For example, the python-dev mailing list is about the Python language’s design features, so it isn’t a general Python help mailing list. The web page at https://www.python.org/about/help/ can direct you to an appropriate place to ask whatever sort of Python question you have.
The benefit of posting your question to an online forum is that future programmers who have the same question can find it and its answers using an internet search. Be sure to use a headline that summarizes the question to make it easy for search engines to organize. A generic headline like “Help please” or “Why isn’t this working?” is too vague. If you’re asking your question in an email, a meaningful subject line tells your helper what your question is as they scan their inbox.
The question “Why doesn’t my program work?” omits the critical detail of what you want your program to do. This isn’t always obvious to your helper, because they don’t know what your intention is. Even if your question is just “Why am I getting this error?” it helps to also say what your program’s end goal is. In some cases, your helper can tell you if you need an entirely different approach, and you can abandon your problem rather than wasting time trying to solve it.
Be sure to copy and paste the entire error message, including the traceback. Merely describing your error, such as “I’m getting an out of range error,” doesn’t provide enough detail for your helper to figure out what is wrong. Also, specify whether you always encounter this error or if it’s an intermittent problem. If you’ve identified the specific circumstances in which the error happens, include those details as well.
Along with the full error message and traceback, provide the source code for your entire program. That way, your helper can run your program on their machine under a debugger to examine what is happening. Always produce a minimum, complete, and reproducible (MCR) example that reliably reproduces the error you’re getting. The MCR term comes from Stack Overflow and is discussed in detail at https://stackoverflow.com/help/mcve/. Minimal means your code example is as short as possible while still reproducing the problem you’re encountering. Complete means that your code example contains everything it needs to reproduce the problem. Reproducible means that your code example reliably reproduces the problem you’re describing.
But if your program is contained in one file, sending it to your helper is a simple matter. Just ensure that it’s properly formatted, as discussed in the next section.
The point of sharing your code is so your helper can run your program and reproduce the error you’re getting. Not only do they need the code, but they also need it properly formatted. Make sure that they can easily copy your source and run it as is. If you’re copying and pasting your source code in an email, be aware that many email clients might remove the indentation, resulting in code that looks like this:
def knuts(self, value):
if not isinstance(value, int) or value < 0:
raise WizCoinException('knuts attr must be a positive int')
self._knuts = value
Not only would it take a long time for your helper to reinsert the indentation for every line in your program, but it’s ambiguous as to how much indentation each line had to begin with. To ensure your code is properly formatted, copy and paste your code to a pastebin website, such as https://pastebin.com/ or https://gist.github.com/, which stores your code at a short, public URL, such as https://pastebin.com/XeU3yusC. Sharing this URL is easier than using a file attachment.
If you’re posting code to a website, such as https://stackoverflow.com/ or https://reddit.com/r/learnpython/, make sure you use the formatting tools its text boxes provide. Often, indenting a line with four spaces will ensure that line uses a monospace “code font,” which is easier to read. You can also enclose text with a backtick (`
) character to put it in the monospace code font. These sites frequently have a link to formatting information. Not using these tips might mangle your source code, making it all appear on one line, like the following:
def knuts(self, value):if not isinstance(value, int) or value < 0:raise WizCoinException('knuts attr must be a positive int') self._knuts = value
In addition, don’t share your code by taking a screenshot or a photo of your screen and sending the image. It’s impossible to copy and paste the code from the image, and it’s usually unreadable as well.
When posting your question, tell your helper what you’ve already tried and the results of those tries. This information saves your helper the effort of retrying these false leads and shows that you’ve put effort into solving your own problem.
Additionally, this information ensures that you’re asking for help, not just asking for someone to write your software for you. Unfortunately, it’s common for computer science students to ask online strangers to do their homework or for entrepreneurs to ask for someone to create a “quick app” for them for free. Programming help forums aren’t made for this purpose.
Your computer’s particular setup might affect how your program runs and what errors it produces. To ensure that your helpers can reproduce your problem on their computer, give them the following information about your computer:
You can find the versions of your installed third-party modules by running pip list
. It’s also a convention to include the module’s version in the __version__
attribute, as in the following interactive shell example:
>>> import django
>>> django.__version__
'2.1.1'
Most likely, this information won’t be necessary. But to reduce the back and forth, offer this information in your initial post anyway.
Here is a properly asked question that follows the dos and don’ts of the previous section:
Selenium webdriver: How do I find ALL of an element’s attributes?
In the Python Selenium module, once I have a
WebElement
object I can get the value of any of its attributes withget_attribute()
:
foo = elem.get_attribute('href')
If the attribute named
'href'
doesn’t exist,None
is returned.My question is, how can I get a list of all the attributes that an element has? There doesn’t seem to be a
get_attributes()
orget_attribute_names()
method.I’m using version 2.44.0 of the Selenium module for Python.
This question comes from https://stackoverflow.com/q/27307131/1893164/. The headline summarizes the question in a single sentence. The problem is stated in the form of a question and ends with a question mark. In the future, if a person reads this headline in an internet search result, they’ll immediately know whether or not it’s relevant to their own question.
The question formats the code using the monospace code font and breaks up text across multiple paragraphs. It’s clear what the question in this post is: it’s even prefaced with “My question is.” It suggests that get_attributes()
or get_attribute_names()
could have been, but aren’t, answers, which shows that the asker has tried to find a solution while hinting at what they believe the true answer to this question would look like. The asker also includes the Selenium module’s version information just in case it’s relevant. It’s better to include too much information than not enough.
Independently answering your own programming questions is the most important skill a programmer must learn. The internet, which was built by programmers, has a wealth of resources that provide the answers you need.
But first, you must parse the often cryptic error messages that Python raises. It’s fine if you can’t understand the text of an error message. You can still submit this text to a search engine to find the error message’s plain English explanation and the likely cause. The error’s traceback will indicate where in your program the error occurred.
A real-time linter can point out typos and potential bugs as you write code. Linters are so useful that modern software development effectively requires them. If your text editor or IDE doesn’t have a linter or the ability to add a linter plug-in, consider switching to one that does.
If you can’t find the solution to your problem by searching the internet, try posting your question to an online forum or email someone. To make this process efficient, this chapter provided guidelines for asking a good programming question. This includes asking a specific, well-stated question, providing full source code and error message details, explaining what you’ve already tried, and telling your helper which operating system and Python version you’re using. Not only will the posted answers solve your problem, but they can help future programmers who have the same question and find your post.
Don’t feel discouraged if you seem to be constantly looking up answers and asking for help. Programming is an extensive field, and no one can hold all of its details in their head at once. Even experienced software developers check online for documentation and solutions daily. Instead, focus on becoming skillful at finding solutions, and you’ll be on your way to becoming a proficient Pythonista.