Interactive Python Tutorial

This is an interactive tutorial that teaches the basics of the Python programming language. It’s a complete starter tutorial, at the end of it you should have enough knowledge to start building basic Python applications.

Why Learn Python?

Python is a great language to learn for the following reasons:

  • It’s an easy language to pick up and start working with, you can write your first script with a text editor and 1 line of code. That also makes it a good language for beginner developers.
  • It’s a well organized and clean language
  • It’s used all over the place: Web apps, science, machine learning, etc.
  • Python’s popularity is growing

Exercises

This tutorial has live online coding exercises throughout, you will run your code and the widget will evaluate if your answer is correct or not. This will hopefully solidify what you learn for each sub-section. Please note that sometimes exercises will require some outside information, either by going to one of the links on the page or by Googling for a solution (ability to look up information is an important skill that a developer must have).

Content

1. Setup and Installation

In this section you will learn how to get Python installed and set up on your machine, how to use PIP and Virtual Environments to deal with 3rd party packages, and how to write and run your first Python application.

Installing Python

The first thing you want to do is get Python set up on your computer so that you can start writing and testing code. You will want to make sure that you have Python 3 running and not the older Python 2. Support for Python 2 is ending in 2020 and this tutorial is written with Python 3 in mind.

Linux

It’s possible that your distribution already came with Python installed. Run python3 --version to see what version you have. If it complains that the command cannot be found, you should install the python3 package for your distribution. For example, in Ubuntu it would be:

sudo apt install python3

Windows

Head over to https://www.python.org/downloads/, download the latest version for Windows, and run it. The installer should have an option to add Python to PATH, you should check that. If you don’t do that, then when running Python on the command line, it will complain that it can’t find that command. If you didn’t add it to your path during installation it’s not a big deal, you can always modify your PATH variables.

My preference is to run Python in Linux, I’ve run into instances where packages just didn’t work in Windows, plus I really prefer the Linux command line interface. With Windows Subsystems for Linux (WSL) it’s pretty easy to get a Linux environment going inside of Windows 10.

Mac

To install Python for Mac, head over to https://www.python.org/downloads/mac-osx/ , download the latest Python 3 release, and install it.

Virtual Environments and PIP

There are a couple of things that we need to familiarize ourselves with.

  1. PIP – the Python Package Installer, whenever we want to grab a third party package to use in our project we will be using pip
  2. Virtual Environments – lets you create a Python environment that is specific to your project and install packages there, instead of doing it globally. This way packages for different projects do not interfere with each other.

When you advance past the basics and want to start using 3rd party packages, like Django, Pillow, Numpy, etc. you’ll want to create a virtual environment. You would just use the venv module to create it, here’s an example:

python -m venv venv # Windows
python3 -m venv venv # Linux

On Linux, before this could work, you first need to install python3-venv. In Ubuntu, for example:

sudo apt install python3-venv

After you created your venv, you would need to activate it:

root@b7ada53bda17:~# source venv/bin/activate

Note how after I ran that my command prompt changed:

(venv) root@b7ada53bda17:~#

Now we can install any package we need, for example:

(venv) root@b7ada53bda17:~# pip install django

Would grab the latest version of Django and install it in our virtual environment (It will be located inside of our newly created venv directory).

The nice thing about it is that you can put all your packages in a requirements.txt file, with versions, like so:

(requirements.txt)

django==2.1.7
factory_boy==2.8.1

Then you can commit that file, and now anyone that needs to replicate your environment will create a new environment and just run:

pip install -r requirements.txt

If you need to do other things with PIP, like upgrading or removing packages, take a look at the PIP user guide.

Another thing to note is that once we activate our virtual environment both python and python3 became valid commands in Linux, they both point to the same binary.

Writing Code

Ok, so now you have Python installed, let’s create some basic Python code and run it.

Create a hello.py file in your favorite text editor. My favorite IDE for Python is PyCharm, but you can use VIM, EMacs or any text editor really. Put the following code in there:

(hello.py)

print("Hello World")

Save that file and run it in your command line interface:

python hello.py

If everything worked correctly you should see “Hello World” printed out. Congratulations that’s your first functional Python application!

Python Command Prompt

If you are testing out some code you can also just open up a Python command prompt. Just run this on the command line:

python

And you should see something like this:

Python 3.6.8 (default, Oct  7 2019, 12:59:55)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 

You can now run Python commands right in here

2. Basics

This section will go over some of the basics of the Python programming language – printing, variables, basic data types, indentation, comments and string formatting.

Printing

In the previous section, the last thing you did was print out “Hello World”, using this simple python script:

(hello.py)

print("Hello World")

The print() command let’s you output something to stdout (Standard Output), or just the screen in our case. Very useful for communicating with the user or debugging.

Exercise

Variables and basic data types

Variables are used to store values and pass them around. The basic data types in Python are booleans, strings, and numbers.

# booleans
a = True
b = False

# strings
c = "good morning"

# numbers
d = 15  # an integer
e = -1.5 # a floating point number

You can also convert data types to other types using functions, the most frequently used will be these:

  • str() – convert to a string
  • int() – convert to an integer
  • float() – convert to a floating point number

And of course you can do various operations with variables:

>>> a = 1
>>> b = 2
>>> c = a + b
>>> print(c)
3

>>> a = "hello"
>>> b = "world"
>>> c = a + " " + b
>>> print(c)
hello world

As shown above, you can add two strings together, and you can do all the basics with numbers: add, subtract, multiply, and divide. Sometimes, it’s useful to know if a number is divisible by something, there is a special operator for that, the modulus operator – %, it tell you how much is left over after a division.

>>> a = 11 % 3
>>> print(a)
2

Finally, there is a value that means that there is no value, in other languages it’s called the null value, in Python it’s None. Sometimes you just don’t have a value and you have to signify that you don’t have one. For example, you asked the customer for an optional piece of information, their ethnicity, and they just didn’t give you one. It’s not the same as an empty string or a False, because those are specific values that we don’t want to confuse with no value.

>>> a = None
>>> print(a)
None

Exercise

Indentation and Comments

Indentation

In most C++ based languages, code is structured using squiggly brackets. For example, if you ever look at some Javascript code you will see something like this:

if (a == b) {
    // do lots of stuff here
    .....
}

In Python, code is structured with indentations:

if a == b:
    print("1")
    print("2")
else:
    print("3")

Python is smart and will replace tabs with spaces and everything will work great as long as the number of spaces is consistent. PEP8 (the Python style guide) recommends that you set 4 spaces per tab in your IDE, text editor, etc. Don’t worry most Python editors will already have this done for you, so that you just need to press tab and everything will work.

Comments

While in other languages you might see comments set using // or /**/, in Python comments are set using #, or for multiple lines with '''.

# This is a comment
''' 
And so is
this one
'''

As an aside, you’ll want to comment your code frequently, especially if it’s complicated. You will definitely run into scenarios where either you come back to some code later or someone else does and have no clue why it’s written the way it is.

String Formatting

And the last thing for this section is how to format strings so that you can print them out correctly. For example, let’s say your application does some number crunching, figures out how much a user spent and how much is still available in their account:

You spent $120, you still have $50 available in your account.

At some point you have two variables, spent and available, and you just want to show them to the user:

>>> spent = 120
>>> available = 50
>>> print("You spent ${}, you still have ${} available in your account.".format(spent, available))
You spent $120, you still have $50 available in your account.

What happened above? We just put {} where we wanted our variables to appear and passed our variables, Python then took those variables, converted them to strings, and inserted them where our special symbols were. format() is pretty flexible and can take one variable, multiple variables, named variables, etc.

>>> spent = 120
>>> print("You spent {:.2f}".format(spent))
You spent 120.00

The special symbols tell Python how to convert your variables. For example, {} just tells Python to convert the variable to a string, but {.2f} tells it to format a float with 2 decimal points.

There are lots of different ways you can format your variables: you can add commas to numbers for readability, left/right justify, align, etc. You can find the full list of features at https://docs.python.org/3.4/library/string.html#format-specification-mini-language

Exercise

3. Data Structures

This section will discuss the most common Python data structures and how to work with them – lists, tuples, sets and dictionaries.

Lists

The most frequent data structure that you will work with is a list. A list is just a collection of values. It’s both ordered and mutable:

  • Ordered – meaning that the items in the collection have a specific order and items retain the order that you insert them in
  • Mutable – you can change the data after the object is created

Here’s a basic list of integers:

>>> a = [5, 2, 8, 3]

A list can also contain different data types and objects

>>> a = [5, "hello", 8, "there", ['another', 'list']]

We can retrieve items from the list using a numeric index:

>>> a = [5, 2, 8, 3]
>>> a[0] # indexes always start with 0
5
>>> a[2]
8
>>> a[-1] # In python you can retrieve values from the end using a negative value
3

Using various functions you can manipulate the list in various ways:

>>> a = [5, 2, 8, 3]
>>> a.append(6)
>>> print(a)
[5, 2, 8, 3, 6]
>>> print(len(a)) # get the length of the list
5
>>> a.pop() # removes a value from the end
6
>>> print(a)
[5, 2, 8, 3]

For a full set of functions you can always check out the official Python documentation.

One interesting function that you should remember from the above is len(), it’s a built in Python function and you can use it with various data types, not just a list. It basically grabs the length of something – even for a string.

And the last thing that I will show you is how to grab a sublist from a larger list using a range of indexes:

>>> big_list = ['a', 'b', 'c', 'd', 'e']
>>> sub_list = big_list[1:3]
>>> print(sub_list)
['b', 'c']

Using this syntax we asked for values from big_list starting at index 1 and all the way up to, but not including, index 3. ‘b’ is located at index 1 (remember indexes start at 0), and ‘c’ is located at index 2, and that falls in the range we asked for.

Exercise

Tuples

A tuple is also ordered, but unlike a list, it’s unchangeable (immutable).

>>> a = (1, 3)

What do I mean by “unchangeable”? Once it’s created, you cannot modify it. If you want to change a above to be (1, 3, 5), you can’t do that, you have to create a new tuple with the values you want. Although, a tuple can hold mutable data, like lists, which can be changed.

Just like with a list, you can grab values with indexes and ranges.

So why do we need tuples? Tuples have some performance advantages, but overall it’s a different type of data. Where is a list should be more homogeneous, like a list of car objects, a tuple is more like a grouping of related data.

>>> person_info = ("John", 38, "111 S street")
>>> name, age, address = person_info

One more thing, you can easily create a tuple with one value like so:

>>> a = (1,)

Exercise

Sets

Unlike lists and tuples, sets are unordered and every element is unique – no duplication.

>>> a = {1, 3, 4, 3}
>>> print(a)
{1, 3, 4}

Sets are really good for some things, like fast lookups. If you wanted to look something up in a list, you would have to run through the whole list, but sets use hash tables and are optimized for getting data out of them quickly.

>>> invited_guests = ["john", "kate", "mary", "john"]
>>> unique_invited_guests = set(invited_guests)
>>> print(unique_invited_guests)
{'mary', 'kate', 'john'}
# let's check if 'kate' can make it
>>> print('kate' in unique_invited_guests)
True

It is mutable (you can change it), you just can’t retrieve values with indexes, that includes slicing, or ranges.

>>> a = {2, 5}
>>> a.add(3)
{2, 3, 5}
>>> a[1]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'set' object does not support indexing

You can do some other really nice things with sets, like union and interestion

>>> a = {2, 5, 8, 3}
>>> b = {5, 6, 2, 9}
>>> print(a.union(b)) 
{2, 3, 5, 6, 8, 9} # combination of both
>>> print(a.intersection(b))
{2, 5} # only 2, 5 are in both sets
>>> print(a - b)
{8, 3} # 'difference' - are in set a, but not in set b

Check out the full list of set methods

Exercise

Dictionaries

A dictionary is a key/value structure. Like a Set, the key is put into a hash table for fast lookups, and like a Set, it’s unordered and you can only index it by the key.

>>> ages = {'kate': 24, 'john': 53, 'jeff': 20}
>>> print(ages['john'])
53

The value can be any object or data type, but the key has to be an immutable data type. Strings and numbers are most frequently used for keys, but you can also use a tuple for example, as long as all the data inside of it is also immutable.

>>> data = {
...          'kate': ('smith', 23, 'female'),
...         'john': ('brown', 53, 'male')
...}
>>> print(data['kate'][2])
female

Exercise

4. Flow Control

In this section we will learn how to direct the flow of an application. We will look at if/else statements, looping, and functions.

If/Else Statements

Using if/else statements we can control what our application does based on the different conditions that it encounters. Let’s say, for example, that our application sends out invites to a kids birthday party to our contact list, and we want to invite contacts by age.

name, age = "john", 20
# We aren't going to invite john because he didn't pass our condition
if age <= 12:
    print("invited")

You can just use if by itself, or combine it with elif or else for more control:

name, age = "luke", 20
if age > 3 and age <= 12:
    print("invited")
elif age > 12:
    print("not invited")
else:
    print("too young")

As you saw above we were using comparison operators, here are the different operators you can use:

  • == – Equals
  • > – Greater than
  • < – Less than
  • >= – Greater than or equal to
  • <= – Less than or equal to

We can also combine expressions with or and and. The order of operation matters, to be really clear make sure to add parentheses around statements:

if (a > 2 and a <5) or b == 5:
    print("here")

If you want to check for a None value you actually should use a slightly different operator, I won’t get into the whys just yet.

if a is None:
    print("it's None")

if a is not None:
    print("it's not None")

Order of operation makes a difference when evaluating our if/else statement as well.

name, age = "john", 80
if age > 3 and age <= 12:
    print("invited to party #1")
elif age > 12:
    print("not invited")
elif age > 65:
    print("invited to party #2")
else:
    print("too young")

# Will print out "not invited"

Even though John should be invited to party #2, since he is over 65, the age > 12 condition will pass first and it won’t bother with the next elif or else statements. How would you modify the code above so that John gets invited to the right party?

Exercise

Loops

The other major form of control is looping. You will frequently need to loop over lists, rows, dictionaries, files, etc. Even just sitting around and waiting for something to happen can happen in a loop with a sleep statement. There are two ways to loop in Python, using the while loop and a for loop.

The while loop is used when you are evaluating a condition:

while i < 10:
    print(i)
    i++

The for loop on the other hand is used when you already have something to iterate over:

# read all lines from a file into content
with open(filename) as f:
    content = f.readlines()

# loop over each line and print it out
for line in content:
    print(line)

Continue/Break

You can always break out or continue a loop. Continuing means that you skip the rest of the loop logic and continue from the top. Breaking will just break out of your current loop.

with open(filename) as f:
    content = f.readlines()

for index, value in enumerate(content):
    if index == 0:
        print("our header")
        continue

    print(value)
    if index == 3:
       break

enumerate() will add a counter to each of our lines, remember that indexes always start with 0. In this example, we want to process our header in a different way than the rest of the file, so the first if statement will be true when index == 0 and we are on our header. It will deal with our header and then tell the interpreter to continue from the top of the loop. And we only want to print the first 3 rows so after that we can break out of the loop.

Note that this is the first time we nested controls, there is an if/else statement inside of this loop, watch how we indent lines so that you can always tell which block a statement belongs to.

Other for loop options

You can use the for loop to iterate over a sequence of numbers:

>>> for i in range(0, 5):
...    print(i)
0
1
2
3
4

range() will generate a sequence of numbers from 0 up to but not including 5 for us. You can iterate over a dictionary:

>>> for key, value in {1: 'a', 3: 'c'}.items():
...    print("{}: {}".format(key, value))
1: a
3: c

items() will return a list with keys and values that we can iterate over.

Exercise

5. Code Organization

Using what we learned so far we can go off and write a nice little script that does something, but we can’t write a serious 10k line application. Pretty quickly the code will get unruly and impossible to maintain. This section will go over ways to structure and organize your code in Python.

Why organize your code?

  1. You’ll get to write code once and reuse it in multiple places
  2. You’ll be able to modify code in one place, instead of everywhere in your application
  3. You’ll be able to write small tests to make sure that a specific functionality works as expected based on various inputs
  4. Your code will be nicely broken up, readable, and maintainable

Functions

The smallest organizational unit is a function. Here’s what functions look like in Python:

def function_name():
    # do something
    return 1

You want to make your function names pretty descriptive, so that if anyone is reading your code they know right away why a function was called and what it does. A function can take any number of arguments and returns a value.

def add_two_numbers(num1, num2):
    return num1 + num2

sum = add_two_numbers(2, 3)

A function can have default parameters:

def increment(value, increment_by = 1):
    return value + increment_by

a = 5
a = increment(a)

And if you want to take a number of variable parameters you can use the special *args and **kwargs parameters:

def add_values(*args):
    sum = 0
    for x in args:
        sum += x

    return sum

print(add_values(2, 5, 8, 1))
# would print out 16
def print_named_param(**kwargs):
    for key, value in kwargs.items():
        print("{}: {}".format(key, value))

print_named_param(a=3, b="test")
# output is:
# a: 3
# b: test

* – Packs and unpacks data into tuples

** – Packs and unpacks data into dictionaries

When used as a parameter in a function it will pack them up into those data structures for us.

Exercise

Importing and Modules

Eventually, a sizable amount of functions will also get pretty unruly, that’s where modules come in. A module is just a Python file containing statements and definitions. By putting common functionality into a single file, we can organize our functions into a more cohesive unit. For example, let’s say that we have many network related functions, that we want to pull out into network.py:

(network.py)

def traceroute(ip_address):
    ...

def ping(ip_address):
    ....

Now whenever we want to use some networking code, we’ll just import the functions we need from that module:

from network import ping

ping('192.168.0.1')

There are many built in modules that you will frequently need to use, like datetime, os, sys, csv, zip, …

from datetime import datetime # Python's datetime module

print(datetime.now()) # print what the current time is

Classes and Objects

Object oriented programming is a pretty big topic that I’m not going to get into in depth here, but basically a lot of things you will be working with will be broken down into objects. If you are working on an automotive app you might want to have a car object, and that object will consist of other objects, like engine, wheel, etc. You would then interact with the object itself to get information from it or have it do something.

A class is just specification for an object, it defines what an object will be and how it will behave.

class Car(object):

    def __init__(self):
        self.__color = 'blue'

    def get_color(self):
        return self.__color

    def set_color(self, value):
        self.__color = value

car = Car()
print(car.get_color())

What we did is define a Car class, using that class we instantiated the object itself with Car(). c is now an object that’s independent from the class, you can instantiate many different car objects and modify them:

car1 = Car()
car1.set_color('red')

car2 = Car()
car2.set_color('green')

Those two cars will now have different colors.

You can see that it’s a really nice concept, because now the car keeps track of everything related to the car inside of it, so that our script doesn’t have to.

A couple of other major things about classes, first of all the constructor – __init__. It’s called one time, when we instantiate our class. And it can take arguments:

class Car(object):

    def __init__(self, initial_color):
        self.__color = initial_color

    def get_color(self):
        return self.__color

    def set_color(self, value):
        self.__color = value

car1 = Car('green')
car2 = Car('blue')

Generally, you shouldn’t do much work in the constructor, just initialize properties. The bulk of your work should happen inside of functions.

And the second thing to discuss is inheritance. Your classes can inherit other classes. For example:

class Car(object):

    def __init__(self, initial_color):
        self.__color = initial_color

    def get_color(self):
        return self.__color

    def set_color(self, value):
        self.__color = value

class BlueCar(Car):

    def __init__(self):
        super(BlueCar, self).__init__("blue")

c = BlueCar()
print(c.get_color())

As you can see, BlueCar extends Car and overrides the constructor, giving us a ‘blue’ initial color every time. Inheritance is useful when you want to inherit most of the functionality from the parent class and extend/override some of it.

One thing to note, is that Python is very different from a lot of other language in the fact that it gives you multiple-inheritance, the ability to inherit from multiple classes.

Contact Class

In previous sections we dealt with a Contact tuple that had a full name, city, and phone number. The tuple wasn’t very structured, you had to remember that contact[0] is the full name. Using classes we can make nice structured contacts:

class Contact(object):

    def __init__(self, full_name, city, phone_number):
        self.full_name = full_name
        self.city = city
        self.phone_number = phone_number

contact = Contact('Jane Smith', 'Houston', '555-555-5555')
print(contact.full_name)

Now we can reference fields by name, we can enforce a certain format if we wanted to for all the fields. And we can do things like make __init__ take the first and last name and then have a function get_full_name() that combines them for us.

Exercise

6. Errors and Testing

In this section we will go over how to deal with errors in your application and how to do some basic unit testing to make sure your app is working well in various scenarios.

Errors

There will be many cases where your application will run into an unexpected condition, some will be due to errors in your code, some due to a condition you should have expected but didn’t, and some just can’t be helped, like periodic networking errors. When an error occurs it starts propagating through the call stack. We have the option to catch it at any level or let it propagate up the stack. If nothing catches the exception the application will crash.

How we want to deal with errors is dictated by the requirements of our application. If you are writing a small script, maybe you just want it to crash with an error so that you can correct and re-run. If you are reading from the network and there is an error, maybe you want to back off and retry later.

Let’s look at an example

def call1():
    call2()
    print("here")

def call2():
    with open("random_file.txt", "r") as f:
        print(f.read())
    print("here")

call1()

Running that will crash our app with:

Traceback (most recent call last):
  File "a.py", line 11, in <module>
    call1()
  File "a.py", line 2, in call1
    call2()
  File "a.py", line 5, in call2
    with open("random_file.txt", "r") as f:
FileNotFoundError: [Errno 2] No such file or directory: 'random_file.txt'

The application tried to read a file that doesn’t exist and crashed. As you can see it printed out a stacktrace, the stacktrace tells us which function called which other function and what line the actual error happened on. <module> -> call1() -> call2() is the call stack.

When an error occurs the function stops proceeding as normal, it will never get to any of the print("here") in the code. Instead the error will get thrown from call2() to call1() and then up from there.

We can catch the error at any point in this call stack:

def call1():
    call2()
    print("here")

def call2():
    try:
        with open("random_file.txt", "r") as f:
            print(f.read())
    except Exception as ex:
        print(ex)
    print("here")

call1()

This time we caught the exception and printed out the error message, and the application can proceed as normal. It will print out:

[Errno 2] No such file or directory: 'random_file.txt'
here
here

Or we can catch it in the function above:

def call1():
    try:
        call2()
        print("here")
    except Exception as ex:
        print(ex)

def call2():
    with open("random_file.txt", "r") as f:
        print(f.read())
    print("here")

call1()

except Exception tells it to catch all exceptions that are either the class Exception or are children of Exception. Catching Exception is pretty much a catch all kind of catch, because all exceptions extend that base class or children of that base class. Sometimes we want to get more granular:

try:
    with open("random_file.txt", "r") as f:
        print(f.read())
except FileNotFoundError:
    print("deal with a file that isn't found")
except PermissionError:
    print("deal with a file that we don't have access to")

If the error is not one of the above, it will just keep propagating.

In bigger applications it’s pretty common to have a catch all exception that will log the error, maybe notify someone, and then end execution gracefully (close connections, handles, etc).

You can also throw or re-throw errors:

def call1(input):
    try:
        call2(input)
    except ValueError as ex:
        print("Looks like call2 didn't like our input")
        raise ex # re-throw

def call2(input):
    if input != "cat":
        raise ValueError("I was expecting to get a cat")

call1("dog")

That’s useful when you want to take some action, but still propagate the error up the stack.

And there is also a catchall statement, finally, that will get executed no matter what happens. We can use it to do clean up or to execute code that has to run regardless of an exception.

connection = None
connection = open_connection()
try:
    # do some stuff
    pass
finally:
    close_connection(connection)

Testing

There are two main types of tests, integration tests and unit tests. A unit test tests a single component, a function, to make sure that it operates correctly under different conditions. An integration test tests multiple components together, to make sure that they all work correctly with each other.

unittest is a testing framework that is part of Python and you can use it to create your tests. Let’s write some basic unit tests:

import unittest

def add_two_numbers(num1, num2):
    return num1 + num2

class TestAddTwoNumbers(unittest.TestCase):

    def test_add_two_numbers(self):
        self.assertEqual(5, add_two_number(2, 3))

    def test_add_two_numbers_negative(self):
        self.assertEqual(-5, add_two_number(-2, -3))

unittest.main()

Outputs:

..
----------------------------------------------------------------------
Ran 2 test in 0.000s

OK

Integration testing would be done the same way, except that it would involve multiple components instead of a single function.

You want to make sure that your tests run quickly, because you want to run a lot of them very frequently. And when you are unit testing you want to make sure that you are testing things in isolation. But things are not always that clean. For example:

def get_contacts_by_first_name(facebook, first_name):
    all_contacts = facebook.get_my_contacts()
    return [x for x in all_contacts if x.split(" ")[0] == first_name]

The function takes a third party service, facebook, grabs and filters a list of contacts. If you were to unit test this function it would be a very long call to an external service, it wouldn’t be testing just the functionality of get_city_contacts() but also of facebook.get_my_contacts(), and it would be grabbing a list that we can’t really control in our unit testing code. That’s not what we want. To get around this we would want to mock facebook with a fake object that returns a list of contacts that we set up just for this test.

import unittest
from unittest.mock import Mock

def get_contacts_by_first_name(facebook, first_name):
    all_contacts = facebook.get_my_contacts()
    return [x for x in all_contacts if x.split(" ")[0] == first_name]

class TestContacts(unittest.TestCase):

    def setUp(self):
        ''' gets called before every test, good for common mock set up '''
        self.facebook = Mock()
        self.facebook.get_my_contacts = Mock(return_value=["Sarah Bloom", "Jeff Brown", "Sarah Smith"])

    def test_get_contacts_by_first_name(self):
        contacts = get_contacts_by_first_name(self.facebook, "Sarah")
        self.assertEqual(sorted(contacts), sorted(["Sarah Bloom", "Sarah Smith"]))

unittest.main()

This way we can also test get_contacts_by_first_name‘s functionality based on what it receives from Facebook. What if it got back an empty list, is it still going to work ok or crash?

Exercise

7. Input and Output

We already discussed outputs a little bit, in the basics section we talked about print and formatting string output. This section will dive deeper into the kinds of inputs and outputs there are and demonstrate how to use a few of them.

Console

So far you’ve been using the print method to output text to the console, you have always been outputting text to stdout (Standard Output). You can also output text to stderr (Standard Error). It’s general practice to output all regular text to stdout and all errors to stderr, then someone can do things like pipe all error messages to a file. To output something to stderr `in Python you can just write:

print("error", file=sys.stderr)

To read data from the user you can read from stdin (Standard Input).

user_input = input()

This call will block until the user enters in some text.

File IO

You will also be frequently reading/writing to and from files. To read from a file you can use the following syntax:

with open("file.txt", "r") as f:
    data = f.read()

open() is a built in method that you can use to open files. with is a special python statement that makes sure that the file is closed after the block ends. Without with you would have to make sure to close the file in a finally catch.

We can also iterate over a file line by line, like so:

with open("file.txt", "r") as f:
    for line in f:
        # do some stuff
        pass

To write to a file we would open it with a ‘w’ flag:

with open("file.txt", "w") as f:
    f.write("some text")

We can also append to a file with an a flag

Exercise

Web Requests

For basic web requests we will be using the requests library, since it makes web queries so much simpler in Python. If you want to grab a page from the web all you have to do is:

import requests

r = requests.get('https://www.google.com')
print(r.text)

Doing a POST is just as simple:

import requests

payload = {'key1': 'value1', 'key2': 'value2'}
r = requests.post("https://httpbin.org/post", data=payload)
print(r.text)

8. Going Forward

This tutorial should give you enough information to get you started. Once you have the basics it’s all just a matter of practice. Just start building your app, when you don’t know how to do something use Google to find an answer, there’s a 99% chance that someone else has already had the same problem and someone already explained how to solve it.

When you get really stuck there are also communities that will look over your code and tell you how to move forward, like https://www.reddit.com/r/learnpython/.

If you want to dive into some more advanced Python material take a look at https://realpython.com/tutorials/advanced/.

Leave a Reply