Author: Chi-chi Wang
Date: May 2019 - June 2019
- Python 2 vs Python 3
- Types, Statements, Loops, Exceptions
- Functions, Files, Yield, Lambda
- Object Oriented Programming
- Comment Block Convention
- Installing Python Packages
- Flask
- Tips And Tricks
TLDR: Just use Python 3.
Python 3 adoption did not go smoothly. Today (May 2019) you will still see Python 2 being used, mostly 2.7 versions.
Python 2 | Python 3 |
---|---|
Last version 2.7.13 released in Dec. 2016 | Released Dec. 2008 |
Maintained, but no new features | New features being added |
End of Life (EOL) in 2020 | Unicode supported by default |
Still default on many systems (ie: Ubuntu 18.04) | Cleared some Python 2 confusion |
Syntax: print "Hello World" |
Syntax: print("Hello World") |
Minor differences from Python 2 |
Not a lot of differences in terms of syntax.
- Dynamically typed
- Types are inferred by the compiler
- Type hinting
- New feature added in Python 3.5
- Allows you to annotate types
def add_numbers(a: int, b: int) -> int:
return a + b
- Only used to inform editors and IDEs
- Does not prevent code from running if incorrect types are used
- Important to write a lot of unit tests for Python scripts
Integers are numbers. Floats are decimal numbers.
Defining an integrer:
answer = 42
Defining a float:
pi = 3.13159
Python 3 also introduces complex numbers as a type.
Python will not throw a type error when doing operations between number types:
answer + pi # 45.14159
Casting a value to a type:
int(pi) == 3
float(answer) == 42.0
Python 3 defaults strings to Unicode text. Python 2 defaults to ASCII.
Strings can be defined with single quotes '
, double quotes "
, or three times quotes (single or double) '''
"""
:
'Hello World' == "Hello World" == """Hello World"""
Three times quotes are often used as function or class documentation. While #
are used for comments in Python there are no operators for multi-line comments. Three times quotes are used for multi-line strings: it is acceptable to use them as multi-line comments that are not assigned to any variables. Your editor may even recognize it as a comment.
Useful string methods in python:
"hello".capitalize() == "Hello"
"hello".replace("e", "a") == "hallo"
"hello".isalpha() == True
"123".isdigit() == True # Useful when converting to int
"some,csv,values".split(",") == ["some", "csv", "values"]
String format functions are used to interpolate values into strings:
name = "World"
machine = "HAL"
"Nice to meet you {0}. I am {1}".format(name, machine)
Python 3.6 introduces string interpolation, known as format string literals:
f"Nice to meet you {name}. I am {machine}"
Variables can be declared as boolean by assigning them as True
or False
:
python_course = True
java_course = False
Booleans can be converted to integer or string:
int(python_course) == 1
int(java_course) == 0
str(python_course) == "True"
None
is similar to null
in other languages:
aliens_found = None
None
is useful as a placeholder value, for variables that are defined in advance but not yet assigned a value. None
evaluates to False
in conditional statements.
None
's type is called NoneType
:
type(None) # <class 'NoneType'>
Normal if
statement in python:
number = 5
if number == 5:
print("Number is 5")
else:
print("Number is NOT 5")
if
and else
statements both end with a colon :
. This is a requirement in python.
We use the ==
to check for equality. We can also use the operator is
to see if two objects are pointing to the same value in memory.
Python can also evaluate a value for truthiness and falsiness:
- Any number other than
0
has a truthy value - Any non-empty string has a truthy value
- Any non-empty list has a truthy value
None
has a falsey value
!=
is the conditional operator to check for lack of equality. not
is the negation operator and is not
can be used to check the negative case of the is
operator:
if not python_course:
print("This statement will NOT evaluate")
and
and or
operators in python can be used to check multiple conditions:
number = 3
python_course = True
if number == 3 and python_course:
print("This will evaluate")
if number == 17 or python_course:
print("This will also evaluate")
Python does not use the ?
operator like other languages for ternary statements:
a = 1
b = 2
print("bigger" if a > b else "smaller") # "smaller"
To define a list in python, use the square brackets:
student_names = [] # empty list
student_names = ["Mark", "Katarina", "Jessica"] # populated list
Lists are 0-indexed and can be accessed by index:
student_names[0] == "Mark"
student_names[2] == "Jessica"
Accessing elements from the end of the list is done with negative indicies. To access the last element in a list use an index of -1
:
student_names[-1] == "Jessica"
student_names[-3] == "Mark"
To add elements to the end of a list, use the append
method:
student_names.append("Homer")
student_names == ["Mark", "Katarina", "Jessica", "Homer"]
To check for the existence of an element in a list use the in
operator:
"Mark" in student_names == True
To check the length of a list use the python function len()
:
len(student_list) == 4
Lists in python are similar to arrays in other languages, but with added benefits: having multiple types in a single list is allowed. As a best practice it may be a good idea to restrict a list to single type - it could help avoid potential bugs.
To remove an element from a list, use the del
keyword:
del student_names[2]
student_names == ["Mark, "Katarina", "Homer"]
Slicing lists will not mutate the list. The operation is done with the :
in the index of a list:
student_names = ["Mark", "Katarina", "Homer"]
student_names[1:] == ["Katarina", "Homer"]
student_names[:-1] == ["Mark", "Katarina"]
student_names[1:-1] == ["Katarina"]
Syntax for a for
loop:
for name in student_names:
print("Student name is {0}".format(name))
You can also use the range
function in python:
x = 0
for index in range(10):
x += 10
print("The value of x is {0}".format(x))
range()
takes a value and creates a list whose elements begin at 0
and end at value - 1
:
list(range(10)) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
range()
also supports two arguments, a starting value and a value to end before:
list(range(5, 10)) == [5, 6, 7, 8, 9]
range()
also supports three arguments, with the last value determines the increments:
list(range(2, 10, 2)) == [2, 4, 6, 8]
break
will cause your loop to stop executing and exit without reaching the end of the list or range function:
student_names = ["James", "Katarina", "Jessica", "Mark", "Bort", "Frank Grimes", "Max Power"]
for name in student_names:
if name == "Mark":
print("Found him! {0}".format(name))
break
print("Currently testing {0}".format(name))
continue
tells a loop to exit the current iteration and continue to the next:
for name in student_names:
if name == "Bort":
continue
print("Currently testing {0}".format(name))
while
loops can also make use of break
and continue
. while
loop syntax:
x = 0
while x < 10:
print("Count is {0}".format(x))
x += 1
while
loops check the condition before even entering the loop.
Allows the storage of key/value pairs. Very similar to JSON (hash?).
Syntax for defining a dictionary:
student = {
"id": 15163,
"name": "Mark",
"feedback": None
}
Dictionaries are very valuable when you need to store structured data. You can group several dictionaries together in a list:
all_students = [
{ "id": 15163, "name": "Mark" }
{ "id": 63112, "name": "Katarina" }
{ "id": 30021, "name": "Jessica" }
]
Syntax for reading a dictionary value:
student["name"] == "Mark"
Python will raise an exception for non-existent keys:
student["last_name"] == KeyError
You can set default values for keys to avoid key errors:
student.get("last_name", "Unknown") == "Unknown"
student["last_name"] == "Unknown"
Retrieve a list of keys or values of a dictionary:
student.keys() == ["id", "name", "feedback"]
student.values() == [15163, "Mark", None]
Removing a key is the same as removing an element in a list, using the del
operator:
del student["name"]
student == { "id": 15163, "feedback": None }
Exceptions are events that occur during your program's excution that cause your program to stop executing. It generally means that some error has occurred and your program does not know how to deal with it.
There are ways to handle exceptions:
student = {
"id": 15163,
"name": "Mark",
"feedback": None
}
try:
last_name = student["last_name"]
except KeyError:
print("Error finding last_name")
print("The program was not halted by the exeception")
Catch multiple exceptions using multiple except
blocks:
try:
last_name = student["last_name"]
numbered_last_name = 3 + last_name
except KeyError:
print("Error finding last_name")
except TypeError:
print("Illegal add operation")
Catch all exceptions with except Exception:
. This will handle any exception that comes its way. Generally you do not want to catch general exceptions, you want to catch specific exceptions as a matter of best practice.
You can access the error object this way:
try:
last_name = student["last_name"]
except KeyError as error:
print("Error finding last_name")
print(error)
This does not give you access to the full stack trace, for that you would need to use the python traceback
module. This will be covered in a later section.
You can also raise your own exception, create any exception you want, and use a finally
handler after any exception that may occur.
Overview of other data types in python:
complex
- Complex numbers
long
- Only in Python 2
- Replaced by
integer
in Python 3
byes
- Sequence of integers in the range of 0..255
- Sequence of strings or other objects
bytearray
- Similar to
bytes
- Similar to
tuple
- Similar to lists
- Are immutable
set
andfrozenset
- Similar to lists, but only have unique objects
- Can be used to eliminate duplicate elements in a list:
set([3, 2, 3, 1, 5]) == (1, 2, 3, 5)
def
keyword is used to define a function:
students = []
def add_student(name):
students.append(name)
return
can be used to return values from a function:
def get_students_titlecase():
students_titlecase = []
for student in students:
students_titlecase.append(student.title())
return students_titlecase
student_list = get_students_titlecase()
If a return value is not specified, None
is returned.
It is convention to follow function definitions with 2 newlines.
Arguments are scoped to the function. Arguments are required by default and the Python parser will throw an exception if the arguments specified by the function definition are not provided in the function call.
In order to make an argument optional you must provide a default value:
students = []
def add_student(name, student_id=332):
students.append({ "id": student_id, "name": name })
add_student("Mark") # Will not throw an exception
Named arguments in calling a function specifies the name of the arguments and may lead to legibility for developers:
add_student(name="Mark", student_id=15)
A function can be defined to accept a variable number of arguments using *
:
def var_args(name, *args):
print(name, args)
var_args("Chi-chi", "Is Learning Python", None, "Hello, World", True)
# Chi-chi ('Is Learning Python', None, 'Hello, World', True)
In this setup the arguments are stored in a list and must be iterated over to parse through the contents.
Keyword arguments can be used to define a variable number of named arguments using **
:
def var_kwargs(name, **kwargs):
print(name, kwargs)
var_kwargs("Mark", id=15, description="Python Student", feedback=None, pluralsight_subscriber=True)
"""
Mark {
'id': 15,
'description': 'Python Student',
'feedback': None,
'pluralsight_subscriber': True
}
"""
Keyword arguments store the named arguments as a dictionary and can be accessed as such.
Python has a built-in input function to allow users to input values on the command line:
def print_args(**kwargs):
print(kwargs)
name = input("Name: ")
id = input("ID: ")
print_args(name=name, id=id)
"""
Name: Mark
ID: 15
{'name': 'Mark', 'id': '15'}
"""
You can nest functions inside of other functions to avoid polluting scope:
def get_students():
students = ["mark", "james"]
def get_students_titlecase():
students_titlecase = []
for student in students:
students_titlecase.append(student.title())
return students_titlecase
students_titlecase_names = get_students_titlecase()
print(students_titlecase_names)
Function closures exist in Python. Nested inner functions have access to variables defined in their outer functions.
Python's built in open
function can be used to access files and perform operations:
def save_file(filename, text):
try:
f = open(f"{filename}.txt", "a") # "a" is an access mode argument
f.write(text + "\n")
f.close()
except Exception:
print("Could not save file")
Access modes in Python:
flag | mode |
---|---|
"w" | write: overwrites the entire file |
"r" | read: read a text file |
"a" | append: append text to a new or existing file |
"rb" | read binary: read a binary file |
"wb" | write binary: write to a binary file |
Other IO operations in Python.
Reading a file in Python:
def read_file(filename):
try:
f = open(filename, "r")
text = f.read()
f.close()
return text
except Exception:
print("Could not read file")
It is always a good idea to wrap any file operations in a try-except block.
Resource: Generators - Python Wiki
Example of building an iterator (generator pattern):
class first_n(object):
def __init__(self, n):
self.n = n
self.num, self.nums = 0, []
def __iter__(self):
return self
# Python 3 compatibility
def __next__(self):
return self.next()
def next(self):
if self.num < self.n:
cur, self.num = self.num, self.num + 1
return cur
else:
raise StopIteration()
sum_of_first_n = sum(first_n(1000))
Negatives of the above pattern:
- There is a lot of boilerplate
- The logic has to be expressed in a convoluted way
Python provides generator functions as a shortcut to building iterators:
def first_n(n):
num = 0
while num < n:
yield num
num += 1
sum_of_first_n = sum(first_n(1000))
Lambda function notation is supported in Python:
# Standard python function notation:
def double(x):
return x * 2
# Lambda function notation:
double = lambda x: x * 2
Lambda functions are simple 1-liners in Python.
Lambda functions are useful in higher-order functions: functions that take another function as an argument.
Python is an object-oriented language. While you are not required to use classes, Python provides them.
There is a debate about whether Python is truly an object-oriented language because it does not provide encapsulation (class methods are not private and hidden from the consumer).
There are no private methods. Most programmers prefix a method with _
or __
to indicate a method should not be accessed directly.
A logical group of functions and data.
Defining a class:
class Student:
pass # in Python pass is a no-op
Creating a new instance of a class Student
:
student = Student()
Defining a class with instance and class attributes and methods:
class Student:
school_name = "Springfield Elementary"
def __init__(self, name, id):
self.name = name
self.id = id
def __str__(self):
retrun f"<{self.school_name}> #{self.id}: {self.name}"
mark = Student("Mark", 115)
print(mark) # <Springfield Elementary> #115: Mark
Notes:
__init__
is the constructor for classes- All class methods receive
self
as their first argument- This is a reference to the class instance
__str__
is the built-in to-string method for a class- Assign values to
self
to create instance attributes - Class attributes are created by assigning variables inside the class body, but outside of methods
- Also known as static variables
- These are shared across all instances of the class
- Must be accessed via
self
inside of methods (ex:self.school_name
) - Can be directly accessed off of the class itself:
Student.school_name
Defining a child class:
class HighSchoolStudent(Student):
school_name = "Springfield High"
def __str__(self):
return f"[self.school_name] #{self.id}: {self.name}"
def primary_school(self):
return super().school_name
james = HighSchoolStudent("James", 312)
print(james) # [Springfield High] #312: James
print(james.primary_school()) # Springfield Elementary
Notes:
- Pass the parent class into the class definition to create a child class
- Override parent methods by defining the same method on the child class
super()
can be used inside a class method to access the parent class- Methods and attributes on the parent class are accessed from the return value:
super().school_name
- Methods and attributes on the parent class are accessed from the return value:
To import from another file simply import the filename:
import hs_student
james = hs_student.HighSchoolStudent("James")
To import just one class from a module:
from hs_student import HighSchoolStudent
james = HighSchoolStudent("James")
To import all named entities from a module:
from hs_student import *
james = HighSchoolStudent("James")
You can import from your own modules as well as standard and third-party libraries.
Annotating code:
students = []
def add_student(name, id):
"""
Adds a student dictionary to the students list
:param name: string - student name
:param id: integer - student id number
"""
students.append({ name: name, id: id, school: "Springfield Elementary" })
It is convention to annotate your functions and classes using multi-line string blocks. Provide a space between the comment and the function implementation.
Install Python packages with pip. They are then available for any Python program run with the version and instance of Python that pip installed the dependency to.
Flask is a simple package for creating web servers. Flask can render html as well as html containing a templating language called Jinja2.
Virtual environments allow you to set up independent Python environments when working with your Python application. This allows you to install different versions of the same depenencies in isolated environments.
By default pip will only install depdencies globally, which can make running different applications that use different versions of the same dependencies impossible without virutal environments.
From stackoverflow:
PyPI packages not in the standard library
- virtualenv is a very popular tool that creates isolated Python environments for Python libraries. If you're not familiar with this tool, I highly recommend learning it, as it is a very useful tool, and I'll be making comparisons to it for the rest of this answer.
It works by installing a bunch of files in a directory (eg: env/
), and then modifying the PATH
environment variable to prefix it with a custom bin
directory (eg: env/bin/
). An exact copy of the python
or python3
binary is placed in this directory, but Python is programmed to look for libraries relative to its path first, in the environment directory. It's not part of Python's standard library, but is officially blessed by the PyPA (Python Packaging Authority). Once activated, you can install packages in the virtual environment using pip
.
-
pyenv is used to isolate Python versions. For example, you may want to test your code against Python 2.6, 2.7, 3.3, 3.4 and 3.5, so you'll need a way to switch between them. Once activated, it prefixes the
PATH
environment variable with~/.pyenv/shims
, where there are special files matching the Python commands (python
,pip
). These are not copies of the Python-shipped commands; they are special scripts that decide on the fly which version of Python to run based on thePYENV_VERSION
environment variable, or the.python-version
file, or the~/.pyenv/version
file.pyenv
also makes the process of downloading and installing multiple Python versions easier, using the commandpyenv install
. -
pyenv-virtualenv is a plugin for
pyenv
by the same author aspyenv
, to allow you to usepyenv
andvirtualenv
at the same time conveniently. However, if you're using Python 3.3 or later,pyenv-virtualenv
will try to runpython -m venv
if it is available, instead ofvirtualenv
. You can usevirtualenv
andpyenv
together withoutpyenv-virtualenv
, if you don't want the convenience features. -
virtualenvwrapper is a set of extensions to
virtualenv
(see docs). It gives you commands likemkvirtualenv
,lssitepackages
, and especiallyworkon
for switching between differentvirtualenv
directories. This tool is especially useful if you want multiplevirtualenv
directories. -
pyenv-virtualenvwrapper is a plugin for
pyenv
by the same author aspyenv
, to conveniently integratevirtualenvwrapper
intopyenv
. -
pipenv by Kenneth Reitz (the author of
requests
), is the newest project in this list. It aims to combinePipfile
,pip
andvirtualenv
into one command on the command-line. Thevirtualenv
directory typically gets placed in~/.local/share/virtualenvs/XXX
, withXXX
being a hash of the path of the project directory. This is different fromvirtualenv
, where the directory is typically in the current working directory.
The Python Packaging Guide recommends pipenv when developing Python applications (as opposed to libraries). There does not seem to be any plans to support venv
instead of virtualenv
(#15). Confusingly, its command-line option --venv
refers to the virtualenv
directory, not venv
, and similarly, the environment variable PIPENV_VENV_IN_PROJECT
affects the location of the virtualenv
directory, not venv
directory (#1919).
Standard library
-
pyvenv
is a script shipped with Python 3 but deprecated in Python 3.6 as it had problems (not to mention the confusing name). In Python 3.6+, the exact equivalent ispython3 -m venv
. -
venv is a package shipped with Python 3, which you can run using
python3 -m venv
(although for some reason some distros separate it out into a separate distro package, such aspython3-venv
on Ubuntu/Debian). It serves a similar purpose tovirtualenv
, and works in a very similar way, but it doesn't need to copy Python binaries around (except on Windows). Use this if you don't need to support Python 2. At the time of writing, the Python community seems to be happy withvirtualenv
and I haven't heard much talk ofvenv
.
Most of these tools complement each other. For instance, pipenv
integrates pip
, virtualenv
and even pyenv
if desired. The only tools that are true alternatives to each other here are venv
and virtualenv
.
Recommendation for beginners
This is my personal recommendation for beginners: start by learning virtualenv
and pip
, tools which work with both Python 2 and 3 and in a variety of situations, and pick up the other tools once you start needing them.
Python 3.7 introduces a new breakpoint()
function through the standard library pdb. For more details check the quick tour post on Hackernoon.
Executables of your Python applications can be created using pyinstaller.
To create an exe file, simply enter into the command line while in your project directory:
$ pyinstaller main.py
Replace main.py
with your own entrypoint file.
Pyinstaller will create a /build
and /dist
directory, as well as a main.spec
file, in your project directory. Inside of the /dist/main
directory you will find a main.exe
file.
Building your project this way generates a number of .pyd
and .dll
files in your /dist
directory that are necessary to run your new executable. In order to bundle everything into a single .exe
file run the following in your command line:
$ pyinstaller --onefile main.py
The --onefile
flag tells pyinstaller to bundle everything into a single executable. When you run pyinstaller with this flag, the /dist
directory will only contain a single file: main.exe
.
This executable file will be very large. This is because the Python interpreter along with required Python packages have to be bundled in with this executable.
Distributing just an .exe
file can be a bit strange. Often you will want to provide an installer which will install the program into the Program Files
directory and add it to the start up menu as well. You may also want to include additional files and assets along with your program (help file, README, images, etc).
Inno Setup is a Windows program that will help you create a setup wizard for any program.