Code Quality
Code Quality
•Sometimes you may add some spaces even if you don't really need it. This will reduce the readability of
your code.
Avoid extra spaces within parentheses.
Good:
•print('Hello!')
Bad:
•print( 'Hello!' )
Avoid an extra space before an open parenthesis.
Good:
•print('some text')
Bad:
•print ('some text')
Quotes
•As it was already mentioned, you can use either single or double quotes to define strings. Please,
choose one that you like the most and consistently use it in your code.
•The only recommendation in PEP 8 is that if a string contains single or double quotes, you should
use the other one to avoid backslashes.
Good:
•print("It's a good string!")
Bad and harder to read:
•print('It\'s a bad string!')
•Backslash in this case is an escape character used to indicate that the following single quote is not
the end of the string; you will learn about it in more detail later. For now, note that the first example
is easier to read.
Which of the following rules are provided by
PEP 8?
Select all codes that follow the PEP 8 guidelines
Where can you find PEP 8?
Find out why the code does NOT comply with PEP 8
Docstrings
• You already know that comments are very useful to explain the inner logic of your code
and clarify implicit steps.
• Comments are often intended for other people working on your program.
• To briefly describe the object's functionality and how to use it, you can provide a
general description via a docstring, the main unit of documenting in Python.
• Docstrings have several Python Enhancement Proposals dedicated to them, so we will
also sum up the conventions of PEP 257 and PEP 287
What is a docstring?
import statistics
statistics.median.__doc__
# Return the median (middle value) of numeric data.
#
# When the number of data points is odd, return the middle data point.
# When the number of data points is even, the median is interpolated by
# taking the average of the two middle values:
#
# >>> median([1, 3, 5])
# 3
# >>> median([1, 3, 5, 7])
# 4.0
• Alternatively, we can call the help() function on the object:
help(statistics.median)
# Help on function median in module statistics:
#
# median(data)
# Return the median (middle value) of numeric data.
#
# When the number of data points is odd, return the middle data point.
# When the number of data points is even, the median is interpolated by
# taking the average of the two middle values:
#
# >>> median([1, 3, 5])
# 3
# >>> median([1, 3, 5, 7])
# 4.0
• What is more, with help(), you can access a docstring for the object even without importing it. To do so, you just need to
pass its name in quotes: for example, help('statistics.median').
Errors
• The first thing you need to know about programming is how to print "Hello, world!". The second one is that this might
be a challenging task, as even such a tiny script can contain various errors. Here you are:
• Traceback is a stack trace that appears when your code causes an error and it reports detailed information on that
particular error, indicating the specific files in which the error occurred. Nonetheless, the lines that are the most
informative for us right now are the last two. They point out the mistake in your code.
• This might seem a little bit frustrating, but generally what errors are designed for is to allow Python to communicate
with you. Whenever you see those red threatening lines – don't panic! Just read carefully what they are saying.
Syntax Errors
• In the example earlier, we can clearly see the magic word SyntaxError that is likely to haunt you throughout the
period of getting used to Python.
• A large variety of different errors are referred to as syntax errors. What they usually mean is that Python has
encountered a problem while trying to compile your program and it can't even execute it.
• If you carefully read the text of a traceback, it will help you to find mistakes and correct them quite easily, as
you can see an arrow pointing to the exact place where Python found the mistake in your code.
• Every syntax error has an associated value. It describes an error in detail.
• In the example, "EOF" in the message "SyntaxError: unexpected EOF while parsing" stands for the end of a
file. This message means that something else had been expected after your statement, but you didn't pass it to
the interpreter. In our case, there should've been a closing round bracket ")".
• Mistakes won't be so obvious all the time. It is quite likely that the message you'll get as an associated value
will be the most common and obscure "invalid syntax", which isn't really helpful. Well, anyway, to locate the
problem it’s enough to know that the error is in the syntax.
Common error for beginners
• Check the following piece of code, for example. It looks like a Petri dish of syntax errors:
• As you can see, there are plenty of syntax errors in this tiny piece of code.
• If you’ve checked and corrected everything from the list above and yet you encounter those error messages –
don’t worry! Once again, it’s just Python trying to tell you that something went wrong.
Exercise
• Take a look at the following code. Did we declare all variables correctly,
according to Python's syntax? Try to fix all mistakes, so
that SyntaxError will not be raised during the initialization of these
variables.
Exceptions
• By now, your code has become syntactically correct and beautiful, it is being executed without any troubles.
Great! But wait, when you continue writing the program, something else appears in the messages from
Python! And the program is only partially executed. What is going on here? Let’s figure this out.
• Some errors in your code will not prevent the program from running. It will only crash while trying to
execute a “broken” line: a line with an error called an exception. Thus, exceptions are the errors detected
during the program execution (interpretation), whereas syntax errors are those detected during compiling the
program into byte-code.
• Let's examine the following piece of code:
Exceptions
• Indeed, we can see that some of the prints worked, while others (print() which contains the error, and yet
another one after it) didn’t. Thus, all the code before the exception is executed properly, and
everything after is not.
• You already know that the most important and informative part of an error in Python is the last line. It
contains a clear and distinct description of the error Python has encountered. In this case, we can
see ZeroDivisionError, which is quite informative, right? One can’t divide by zero.
• Similar to syntax errors, nearly all the built-in Python exceptions have an associated value that indicates the
detailed cause of the error. Exceptions are not unconditionally fatal: later you will learn how to handle them.
Most common exception for learners
• Perhaps, the most common exceptions people see while they are still
learning Python are NameError, TypeError and ValueError.
NameError
• is usually raised when you haven’t defined your variable before using it or you have done it
incorrectly.
• Consider this piece of code:
• The associated value sometimes tells you the exact problem, so it's going to be really easy to fix
it.
TypeError
• Here we’re trying to sum up a string and an integer, which will cause an exception again:
ValueError
• can be raised if you’re trying to use a variable of the correct type, but with
inappropriate value.
• For example, it's the case when you are trying to make an integer of a string, which has no
integer value:
• If you have any troubles trying to understand what the error is, you can always copy-paste
the last line of a traceback and google it.
• Moreover, you're strongly encouraged to do so, as 99% of troubles that the learners face
have already been solved on specialized forums.
Exercise
• You have a program that is supposed to take two inputs from the user
and turn them into integers to be compared later. Fix all the errors in
this program, make it properly compare variables, and print the
result.
Built-in Exceptions
• Don't be afraid, we know, it's hard to remember the hierarchy at once. You can do it step-
by-step when you have free time.
• Below we try to focus on the main features of the structure you need to know.
• First of all, remember that the BaseException class is a base class for all built-in
exceptions, which we can consider as the root of all exceptions.
• This exception is never raised on its own and should be inherited by other exception
classes.
• In terms of this hierarchy, other classes are known as subclasses of the root class. For
instance, the IndexError is a subclass of the LookupError class. At the same time,
the LookupError itself is a subclass of the Exception class.
Hierarchy of Exceptions
• The BaseException subclasses
include SystemExit, KeyboardInterrupt, GeneratorExit and Exception.
• The SystemExit is raised when the sys.exit() function is used to terminate the program.
• The KeyboardInterrupt is raised when the user hits the interrupt key, e.g. Ctrl+C during
the execution of a program.
• The GeneratorExit appears when your generator is being closed (it will be covered
later).
• The most common subclass is the Exception class. It contains all exceptions that are
considered as errors and warnings.
Built-in Exception Types
• Before we start analyzing the pieces of code, take a look at the table with the built-in exceptions that
programmers often meet in their code:
SyntaxError
• The list doesn't have the right-side bracket ], that's why the SyntaxError is raised. Below is another
example:
• We tried to concatenate a string and an integer, that's why the TypeError was raised. For
example, if you read input and forget to convert it into an integer before performing some
operations:
ValueError
• In the code below, the input accepts a string that can't be converted to an integer, that's why
the ValueError is raised:
• The method remove() can't delete the specified string from the list because there's no such element
there.
OSError
• The next example illustrates the OSError. Some subclasses of this error may
appear when you work with files. For example, if we try to open a file that
doesn't exist, the FileNotFoundError will be raised:
• Note that in this case, the ModuleNotFoundError is a subclass of the ImportError. Why so? The
module math exists in the first example, but there's no such function as square.
• In Python, there's no special error subclass for this situation, so a more general ImportError is
raised. In the second example, however, we try to import the module that doesn't exist in Python,
so the ModuleNotFoundError is raised.
EOFError
• Now let's discuss the EOFError. We have mentioned that it is raised when the input has no data
to read. You can run into this error when, for instance, you have 2 integers as an input, one per
line, but you call input() three times:
• The function print() is misspelled, so Python does not recognize it. The situation is the same
when you mess up the variable names:
IndexError
• The list in the example above contains the only element, but we try to get an element with
the index equal to 1. Mind that indexing in lists starts with 0. That's why the IndexError is
raised.
• This is a very common mistake with lists. Check the indexes you are passing to your list with
care and mind the number of values it has in total.
Debugging
SyntaxError
Match the Description
Handling Exceptions
• Imagine a simple calculator, which can only divide numbers, where we ask a user
for input of two numbers and then print the result.
• Once again:
Handling Exceptions
• Again we encounter this annoying traceback, and our program crashes completely! For
preventing this, we need to use try-except statements in the place, which can be a possible
source of errors. Here is the place where we make the division — the variable result, so let's
brace it with try-except blocks:
Exception Handling Keywords
• Here you can see not only try and except keywords but also else and finally. The full exception
handling block works as follows:
• First, Python executes the try block: everything between try and except.
• If there is no exception, the try block is successfully executed and finished.
• If an exception occurs, the rest of the try block is skipped. After that, Python checks if the type of
exception matches the exception specified after the except keyword, it executes the except block
and continues executing the program after the try-except block.
• If an exception doesn't match the exception named in the except clause, it is called an unhandled
exception and execution of your program stops with a traceback.
• The else-block in this situation is only executed if there occurred no exceptions.
• There can also be a finally keyword. A finally clause is always executed before leaving the try-
except block, whether an exception has occurred or not.
Handling Several Exceptions
• But what if our user doesn't understand what the "number" is and enters, for example, "one"?
• We see those red disappointing lines again! Why? Well, because we specified only
a ZeroDivisionError exception in our try-except block. And here we have a ValueError exception,
so Python doesn't know how to deal with it in our program.
Handling Several Exceptions
• As you know, the built-in exceptions comprise a hierarchical structure, so you can do the
following and identify no specific exception:
• Thus you'll catch any exception from the list. But it will also work for KeyboardInterrupt and
other useful exceptions and moreover, it's considered to be a bad tone in programming, so it'd
be better to use two or more except blocks for different exceptions:
Handling Several Exceptions
• An except clause also may specify multiple exceptions as a parenthesized tuple, for example:
• Due to the hierarchical structure, one exception can actually catch multiple exceptions. For example:
• Sometimes there can be a situation in which you can't even predict the type of exception in your
code. You have no other choice, but to use the most general exception. For that purpose, instead of
using pure except: statement, like this:
Handling Several Exceptions
• What does it mean to write a program in Python? From the programmer's point of view, it implies writing certain
instructions in a file and then executing it in Python. We can simply create a .txt file with the following statement:
• And then execute it from the command line in the following way (suppose, we named it %example.txt%):
• You may know that all Python files are supposed to have a .py extension, but we can write the code in a .txt file as well.
Usually, programmers don't use text editors for programming, they use IDEs (Integrated Development Environment), a
special program designed for software development. However, one thing remains — the source code is just a set of specific
instructions.
• It's not all that simple. You may have heard that there are interpreted and compiled programming languages. Maybe you've
also heard that Python is an interpreted language. But do you know what that means?
Interpretation
• The process of program execution may seem something like this to you:
• The majority of Python programmers just stop inquiring further from this point.
• During the interpretation part, the software goes through your program and executes the code, line by line. This software is
called an interpreter. It is a part of the standard Python installation package. So, an interpreter is a layer of software logic
between your source code and your hardware.
Interpreted or Compiled
• To understand what is under the hood of Python, let's find out what lies behind the concept of interpreted
and compiled languages.
• With compiled languages, when a programmer wants to run a source code, they need to use a compiler first.
It is the special software that translates the source code into a special language. This language is called
the machine code. It is a set of instructions executed directly by your processor.
• Interpreted languages also include a compilation step, but the compiler turns your source code into the so-
called byte code, an intermediate language. The byte code is a lower level (more detailed), platform-
independent, and more efficient version of the source code.
Interpreted or Compiled
• Byte code is saved as a .pyc file after the compilation. If the source code of the program has not been changed since the
last compilation, Python will load the existing .pyc file, bypassing the compilation step.
• Below you can see the readable byte code. The second column contains the instructions in the order of their execution.
If something is unclear, it's OK. You don't have to know the byte code inside out to write programs.
Select 2 correct statements
Python Virtual Machine
• The byte code is supplied to the PVM (Python Virtual Machine) after the compilation. It may sound quite impressive,
but in fact, it is nothing more than a big piece of code that iterates through the byte code instructions, which were
received from a compiler, and executes them one by one. It is an internal part of the Python system and you don't
need to install it as a separate program.
• This complexity is hidden from a programmer for a reason. The interpretation is fully automated, and you won't need
to think about it at all. Remember that Python programmers simply write the code and run the files, Python does
everything else.
• Python is an interpreted language. It compiles the source code to the byte code before the execution. Executing a
Python program implies both compilation and interpretation.
• When your program is turned into the byte code, it's executed line by line by the PVM. That's why Python is slower
than compiled languages such as C++ or C.
What is the
Python Shell?
• Once you installed Python, you can use it as an interactive
shell.
• The interactive shell is a real-time Python interpreter.
• "Interactive" means that you can type here anything in Python
syntax, press "Enter", and the shell will immediately print the
result.
• It can be helpful for you if you are beginning to learn
programming because with the help of the shell it's easier to
review your code and find accidental errors: you can check it
line by line.
How to Start?
Simple Programs
• Let's try to write some code and see what happens in the shell. For example, you can use it as a simple calculator:
• So, every time you type something after >>> and press "Enter", Python tries to execute it and shows the result. Sometimes
you can get an error:
• Don't be afraid. For now, if you see an error, and you're sure your code should work, simply double-check your code. Maybe
there is a typo, and you just need to fix it.
Simple Programs
• In the previous example, we're trying to concatenate a number and a string, which is not allowed in
Python. We can solve it in two ways:
• See, if we try to sum two strings, we get a new string which contains both of the previous. It's
a concatenation. But if we try to add two numbers we get their sum.
• You can use more complicated math operations or concatenate letters to words and words to sentences.
Exercise a=7
How to read a traceback
• Anyone who has never made a mistake has never tried anything new." — Albert
Einstein.
• A programmer, like any other person, should be very careful to avoid mistakes.
• In real life, you may not understand exactly where you did something wrong, but
in programming, there is a traceback that will point you to the mistake! It is very
important to learn to read it.
How to read a traceback
• Sometimes, a traceback can contain several lines of code, and you need to understand how to find the error. It
happens a lot with functions, so we'll show you this as an example.
• Let's write a function is_positive() that prints whether the given number is positive or negative:
# this code is in the file 'numbers.py'
def is_positive(number):
if number > 0:
return number + " is positive"
else:
return number + " is negative"
print(is_positive(1))
• Can you spot the error?
How to read a traceback
• Let's go up starting from the last line and divide the traceback into parts:
• TypeError: unsupported operand type(s) for +: 'int' and 'str'
The last line of the traceback always contains the name of the exception that was raised and the reason why it might have
happened.
• File "/full/path/to/numbers.py", line 4, in is_positive
return number + " is positive"
Then there's the line of code that triggered the exception. This part of traceback contains the full path to the numbers.py file,
the line number where the error occurred, and the name of the function where this line is located.
• File "/full/path/to/numbers.py", line 9, in <module>
is_positive(1)
This block is similar to the previous one, but it refers to the line of code that called the function with the broken code. The
location of the function call is also given: the module name and the line inside the file. The <module> means that an error
occurred in the executable file.
• As you might have guessed, the number needs to be turned into a string value using the str() function to make this code
valid.
Find a way
• Traceback can be extra useful with other imported modules. Let's create a new file import_numbers.py, import
and then run the very same is_positive() function from our previous module, numbers.py:
• Now we understand that our traceback consists of several blocks. In our example, we
got the following bottom-up structure:
1. The name of the error and its description.
2. The location of the module that contains the "broken" function code line and the line
itself.
3. The location of the executable file and the executed function.
• In practice, a traceback can contain a good number of blocks. It simply means that
many functions were called until the exception was thrown.
“Long” problems
check_numbers([1,-1,"Two"])
• During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/full/path/to/numbers.py", line 20, in <module>
check_numbers([1,-1,"Two"])
File "/full/path/to/numbers.py", line 14, in check_numbers
if int(num) > 0:
ValueError: invalid literal for int() with base 10: 'Two’
• What happened? In fact, this traceback clearly tells us that During handling of the above exception, another exception occurred.
• The first part of the traceback above these words tells us about the TypeError exception, which we tried to catch with the try-
except statement. The thing is, during the execution of this block, another exception was raised, the one that we see in the
second part of the traceback. A new ValueError exception occurred in the except TypeError: block because we tried to pass a
string "Two" to the function int(). That is, when handling the first TypeError exception, we received the
second ValueError exception.
• As you can see, tracebacks can contain a lot of information. It is important not to get lost in it and be able to find errors in your
code, as well as to be ready to go back to our past mistakes and correct them.
Debugging in Shell
• You've already learned how to start the Python shell and experiment there with
modules and functions. But the thing is, the more code you write, the higher chances
to get a bug.
• A bug is an unexpected error in your code that is usually hard to find. The process
of finding and fixing bugs is called debugging, and it's another thing the Python
shell can be used for.
• IDE (Integrated Development Environment) is thought to be better for debugging.
• However, IDLE also provides useful tools for debugging, so let's see how it can be
done.
Debugging
• There may be different reasons to check your program: e.g. you get an exception in your code and don't
understand where it comes from, or the code doesn't work as intended. In such cases it's a good idea to
use the shell to find out what's going wrong.
• Let's see how the debugger works in action. Say, we have written a function to generate passwords:
Debugging
• It works fine but let's improve it a bit so that it doesn't use confusing characters for a password, such as
“0” and “O” or “1” and “l”:
• After these changes our code doesn't work correctly: every password we get contains only those
characters.
Theory
• First, import the modules random and string, then copy the function to IDLE and
press Enter to see the prompt >>> again. To initialize the debugging mode, click
"Debug" --> "Debugger" from the menu above. You'll see “DEBUG ON” in IDLE, and
a new window will appear. Check all checkboxes so that we can investigate all options
the IDLE debugger provides.
• To start the debugging, just call the function from the shell, i.e.
type password_generator(4) and press Enter. The debugger window will change: it
hasn't executed any code yet but we can see the line of code it has paused at, as well as
global variables.
Theory
• Once we understand what's in this window, let's proceed to debugging our function.
• Press the "Step" button several times and carefully look at what is happening in the debugger window.
Once you have reached the line where our function initializes the char variable, check the value of this
variable. If you see that some line wasn't executed, it means that you need to double check the line
with if condition:
Practice
• Ah, that’s it! The condition is wrong, we need to check if it’s NOT in confusing_chars. Sometimes we need to
sleep more, sometimes we need a coffee-break, otherwise, we can make errors in our code.