A Complete Introduction to Python 3.X
28.09.2020 - Jay M. Patel - Reading time ~30 Minutes
Introduction
This is a first draft of an introductory level tutorial on Python 3.x. It is designed to get a user familiar with core elements of the language in less than one hour without getting bogged down in intricate complexities or edge cases.
This tutorial will NOT cover Numfocus based libraries such as Matplotlib, Numpy, Pandas, scikit-learn, etc. You can check out our Pandas tutorial for more information on Pandas and Numpy.
We will also not get into building blocks of any introductory level computer science class which discusses algorithms, big O notation, most efficient sorting implementations etc., abstract data structures such as stacks, queues,linked lists, hash tables, trees etc.
Python contains complete support for object oriented programming (OOP) but to keep things simple we are going to stick with functional programming style in the examples.
We are also going to stay away from machine learning and data science topics such as natural language processing algorithms etc. but you are encouraged to check out those tutorials by going to the menu and navigating to the tutorial of your choice.
Main Content
1.0 Installation
The best way to work with Python is installing the Anaconda distribution for your programming language. It packages the all the relevant libraries that will be useful not just for introductory level discussion on Python but will serve you well even when you move to advanced data science and programming use cases. We will be assuming that you are running Python 3.x for rest of this tutorial series.
2.0 Hello World! and Python Syntax
Once you have anaconda installed, let us take it for a spin by calling Python interpreter from your bash or command line.
> python
Python 3.6.4 |Anaconda, Inc.| (default, Jan 16 2018, 10:22:32) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
You can alternately start the ipython as shown below. This gives access to many more features than the plan python interpreter. Visually, the major difference is >>>
for python interpreter vs []
for the ipython.
> ipython
Python 3.6.4 |Anaconda, Inc.| (default, Jan 16 2018, 10:22:32) [MSC v.1900 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 6.2.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]:
Python has incredibly simple syntax and the major difference for someone coming over from C based languages is that semicolons arent required for single statements on one line.
The hello world statement is very simple:
print('hello world!')
#Output:
hello world!
The only exception to this is if you need to write multiple statements on one line
x = 1; y = 2; print(x + y)
#Output
3
Indentations serve the same purpose as curly brackets “{” for marking end of for/while loops or if/elseif/else statements. If you need one expression to span multiple lines, than you can use either parentheses “()”, brackets “[]“, or curly “{}” braces.
x1, x2, x3, x4 = 1, 2, 3, 4
X = (x1 + x2 +
x3 + x4)
print (X)
testlist = [1,
2,
3]
print (testlist)
# Output
10
[1, 2, 3]
For simple expressions such as print or function calls, you can put a compound statement on same line after the colon
x,y = 3, 2
if x > y: print(x)
#Output
3
3.0 Python Scripts
We can run more complicated Python scripts by saving the code to file, and execute it all at once. By convention, Python scripts are saved in files with a .py extension. For example, let’s create a script called test.py which contains the following:
# file: test.py
print("Running test.py file")
x = 5
print("Result is", 200 * x)
To run this file, we make sure it is in the current directory and type python
filename
at the command prompt:
$ python test.py
"Running test.py file"
Result is 1000
If you prefer a line by line code execution, than you should check out Jupyter notebooks which will allow you to thoroughly test your code and interactively work with output (even plots and charts) from right in your browser. Anaconda distribution already includes a Jupyter notebook so starting a new instance is as simple as calling jupyter notebook
from your command line or bash.
4.0 Python Keywords and Builtins
python only has a handful of keywords, and they can be shown by the command below:
import keyword
print(keyword.kwlist)
# Output
['False', 'None', 'True', 'and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']
The Python interpreter has a number of functions and types built into it that are always available.
import builtins
dir(__builtins__)
# Output:
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '__IPYTHON__', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'display', 'divmod', 'enumerate', 'eval', 'exec', 'filter', 'float', 'format', 'frozenset', 'get_ipython', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
Compared to other programming languages, it is surprising how limited the number of reserved keywords are for Python especially considering that these terms encompass everything from data structures, iterators, decision statements, generators, semantics operators,exceptions and errors etc.
We will now individually go through each aspect of Python and use example code to demonstrate syntax.
5.0 Data Structures
Python has boolean, numerical types such as int
, float
and complex
. There is a data class called decimal
for user alterable precision (defaulting to 28 places) rather than hardware based binary floating point in float
. Python also has sequences such as strings, lists, tuples byte array, bytes (only available in python 3.X; a sequence of integers in the range of 0-255), sets, and mappings such as dictionaries dicts.
In this section, we will introduce data structures available in Python.
5.1 Lists
List are the basic ordered and mutable data collection type in Python.
They can be defined/initialized with comma-separated values between square brackets []
, it can contain items of different types like dicts but usually has ones of same type only. Like all built in data types, List can be indexed and sliced too, and negative index numbers mean that you slice data from left instead of right and all slice operations return new list.
SampleList=[1,2,3,4,5]
print (SampleList)
print (SampleList[1])
print (SampleList[2:])
print (SampleList[-2:])
print (SampleList[:-2])
#Output
[1, 2, 3, 4, 5]
2
[3, 4, 5]
[4, 5]
[1, 2, 3]
Indexing, Slicing, and Offsetting
Python provides access to elements in compound types through indexing for single elements, and slicing for multiple elements. As we’ll see, both are indicated by a square-bracket syntax. Suppose we return to our list of the first several primes:
L = [2, 3, 5, 7, 11]
Python uses zero-based indexing, so we can access the first and second element in using the following syntax. Elements at the end of the list can be accessed with negative numbers, starting from -1:
print(L[0])
print(L[1])
print(L[-1])
#Output
2
3
11
The left offset is taken to be the lower bound (inclusive), and the right is the upper bound (noninclusive). That is, Python fetches all items from the lower bound up to but not including the upper bound, and returns a new object containing the fetched items. If omitted, the left and right bounds default to 0 and the length of the object you are slicing, respectively.
S[:] fetches items at offsets 0 through the end—this effectively performs a toplevel copy of S.
Indexing is a means of fetching a single value from the list, slicing is a means of accessing multiple values in sub-lists. It uses a colon to indicate the start point (inclusive) and end point (non-inclusive) of the sub-array. For example, to get the first three elements of the list, we can write:
print(L[0:3])
# same as
print(L[:3])
#Output
[2, 3, 5]
[2, 3, 5]
Similarly, if we leave out the last index, it defaults to the length of the list. Thus, the last three elements can be accessed as follows:
L[-3:]
#Output
[5, 7, 11]
Finally, it is possible to specify a third integer that represents the step size; for example, to select every second element of the list, we can write:
L[::2]
# equivalent to
L[0:len(L):2]
#Output
[2, 5, 11]
A particularly useful version of this is to specify a negative step, which will reverse the array. A simple way to insert single or multipleelements into the list without deleting existing element at that index is by using L[1:1]
. You can also delete elements from a list by simply assigning it to an empty list.
L[::-1]
#Output
[11, 7, 5, 3, 2]
# deletion at index 1
L = [2, 3, 5, 7, 11]
print(L)
L[1:2]=[]
print(L)
# Inserting two elements at index 1
L[1:1] = [200, 300]
print(L)
#Output
[2, 3, 5, 7, 11]
[2, 5, 7, 11]
[2, 200, 300, 5, 7, 11]
List has methods such as L.sort(key, reverse) (key is attribute to to sorted against and reverse=false (ascending) is default) which modify list in place; i.e. a statement such as L=L.sort() will return None rather than the list. if you need an iterable object, than use sorted(iterable, key, reverse). For sorting based on standard lower case character, set the key = str.lower
The other methods include:
L.reverse()
reverses the order of the listL.count(value)
counts the number of occurances of the valueX in L
checks if element X is present in list L, returns true or false.
adding values
L.append(value)
adds a value to the end of the listL.extend ([x1, x2, x3..])
adds multiple values to the end of the listL.insert(index, value)
adds a value at a particular index
delete
L.pop()
removes the value from the end of the list at offset -1 (also called top of the stack) and returns that value; it can accept index number for a value other than defaultL.remove(value)
removes a first matching value from the listdel L[index]
deletes a value at a specificed index.
L = [1,2,3,4,5]
L.sort(reverse = True)
print("the reversed sorted list is: ", L)
L=L.sort(reverse = True)
print ("assigning L=L.sort will result in:", L)
L = [1,2,3,4,5]
L=sorted(L, reverse = True)
print("the reversed sorted list is:", L)
print ("L.pop() will return this value(default index is -1):", L.pop())
print ("L.pop(1) will return this value:", L.pop(1))
print ("the list after pop(-1) and pop(1) is:", L)
L.reverse()
print("The list after reverse is:", L)
print("is 3 in the list:", 3 in L)
#Output
the reversed sorted list is: [5, 4, 3, 2, 1]
assigning L=L.sort will result in: None
the reversed sorted list is: [5, 4, 3, 2, 1]
L.pop() will return this value(default index is -1): 1
L.pop(1) will return this value: 4
the list after pop(-1) and pop(1) is: [5, 3, 2]
The list after reverse is: [2, 3, 5]
is 3 in the list: True
5.1.1 List Comprehensions
ex = [c * 2 for c in 'python']
ex
# Output
['pp', 'yy', 'tt', 'hh', 'oo', 'nn']
This is same as:
ex = []
for c in 'python': # List comprehension equivalent
ex.append(c * 2)
ex
#Output
['pp', 'yy', 'tt', 'hh', 'oo', 'nn']
5.1.2 zip()
refer to dicts section to see how to zip and unzip multiple lists/dicts/tuples into one.
5.1.3 map()
map()
is an iterable object used to apply functions (incl. lambdas) to sequences. In the example below, map is applying the function round()
to the list L.
Since map()
is an iterable, you’ll have to coerce it into a list if you want to generate the entire sequence.
L = [-3.2, -2.8, -1.3, 0, 1.6, 2.9]
L2 = map(round, L)
print ('the map object is:', L2)
L2 = list(map(round, L))
print ('L2 is:', L2)
#Output
the map object is: <map object at 0x00000213586D9400>
L2 is: [-3, -3, -1, 0, 2, 3]
We have left out discussions on filter()
, list generators etc. but for now this is sufficient for introducing a related data type called strings.
5.2 Strings
Strings are immutable and can be enclosed in single quotes ‘ or double quotes “” and give the same result. A sting has to quoted in double quote if it contains a single quote as part of text and vice versa. Strings can also be indexed and sliced like lists.
word='analytics'
print (word[0])
#Output
a
5.2.1 Basic String Manipulations
These include things like converting to ints, lists, modifying cases, finding substrings etc.
Converting strings to int and list and vice versa
Strings can be converted to integers and vice versa by simply calling int()
or str()
.
int100= int ('100')
str100 = str (100)
print(type(int100))
print(type(str100))
X = 'python'
X = list (X)
#Output
<class 'int'>
<class 'str'>
You can also use repr
to convert int to a string which can be run directly on the terminal.
repr100= repr(100)
print(type(repr100))
#output
<class 'str'>
You can convert a immutable string to a mutable list by simply calling list (X)
; make modifications you want, and convert it back to a string using ''.join(X)
X = 'python'
X = list (X)
print (X)
X.append (' 3.x')
print (X)
stringX = ''.join(X)
print (stringX)
#Output
['p', 'y', 't', 'h', 'o', 'n']
['p', 'y', 't', 'h', 'o', 'n', ' 3.x']
python 3.x
character code conversions to integer code can be performed by ord
; the reverse operation is performed by chr
print(ord ('j'))
print(chr(106))
106
j
strings can be modified by X.replace(‘original’, ‘replacement’) where X is the original string. The other string functions are X.swapcase(), X.find(), X.lower() etc.
X = 'firsttest'
Y=X.replace('first','second')
print(Y)
print (X)
# Note that X.replace returns new object and does not modify X in place, as an example, printing X below will print original string
X = 'firsttest'
X.replace('first','second')
print(X)
#Output
secondtest
firsttest
firsttest
5.2.2 Substring Matching
This can be accomplished by either using in
operator or using the S.find()
method.
S1.find(S2)
allows an user to pass a second string S2 as an argument, if S2 substring match is not found in S1,then it returns a -1, otherwise it returns the lowest index in the string where substring S2 is found (more info).
S1.index(S2)
is similar to find
however, when it cant find a match, it raises a ValueError
instead of returning -1.
S.rfind()
and S.rindex()
methods works similarly, however it searches for the first occurrence from the end rather than the beginning of the string.
def isIn(string_1, string_2): #This is the function that returns true/false
if (string_1.find(string_2)) != -1 or (string_2.find(string_1)) != -1:
is_in = True
else:
is_in = False
return(is_in)
print('a program to check for common string')
a = input('enter a word: ')
b = input('enter a word: ')
z = isIn(a,b)
print(z)
#Output
a program to check for common string
enter a word: at
enter a word: bat
True
More examples
a = 'bat'
b = 'bat2'
c=a.find(b)
d=b.find(a)
if d !=-1 or c != -1:
is_in = True
else:
is_in = False
# check out this for more info on print statement formatting https://stackoverflow.com/questions/17153779/how-can-i-print-variable-and-string-on-same-line-in-python
print("is substring b found in a:\n", is_in)
print ("it is found at index: " + str(c) + "or" + str(d))
#Output
is substring b found in a:
True
it is found at index: -1or0
For the special case of checking for a substring at the beginning or end of a string, Python provides the startswith()
and endswith()
methods.
5.2.3 String Formatting
Checking if a given string is uppercase or lowercase can be accomplished by S.isupper()
and S.islower
. It does not accept any arguments and returns true or false if all characters are upper or lower.
If you want to test if only a subset of the string (or just one character) is uppercase or no, you can combine use the index such as S[1:3].isupper()
or just S[2].isupper()
S = 'DataAnalytics'
print("String", S + " is upper case:", S.isupper())
print("First letter (index 0) of string", S + " is uppercase:", S[0].isupper())
print("Letters 1-3 of", S + " is lowercase:", S[1:3].islower())
#Output
String DataAnalytics is upper case: False
First letter (index 0) of string DataAnalytics is uppercase: True
Letters 1-3 of DataAnalytics is lowercase: True
X.rstrip('arg')
removes characters from the right based on the argument (a string specifying the set of characters to be removed), if arg is empty, it removes whitespaces at the end of the string. we can use it to remove newline characters X.rstrip('\n')
.
X = 'python 3.x is better than 2.x!'
print (X.rstrip('!'))
print(X.rstrip())
#Output
python 3.x is better than 2.x
python 3.x is better than 2.x!
keyword and positional formating can also be done by .format()
method.
#by keyword using format
x = 'These are {number} examples in {name}'.format(number = 2, name = 'python')
print(x)
#by position using format
y = 'These are {0} examples in {1}'.format(2,'python')
print(y)
#Output
These are 2 examples in python
These are 2 examples in python
For numerical inputs, you can include format codes which control how the value is converted to a string. For example, to print a number as a floating point with three digits after the decimal point, you can use the following.
pi = 3.14159
"pi = {0:.3f}".format(pi)
#Output
'pi = 3.142'
As before, here the “0” refers to the index of the value to be inserted. The “:” marks that format codes will follow. The “.3f” encodes the desired precision: three digits beyond the decimal point, floating-point format.
This style of format specification is very flexible, and the examples here barely scratch the surface of the formatting options available. For more information on the syntax of these format strings, see the Format Specification section of Python’s online documentation.
5.2.4 Multiple String Modifications
In the example shown below, %d and %s are used to substitute a decimal number and string respectively by position.
'These are %d examples in %s' % (2, 'python')
#Output
'These are 2 examples in python'
We can also use dictionary based formatting to do the keyword based formatting.
x = 'These are %(number)d examples in %(name)s'%{'number': 2, 'name':'python'}
print(x)
#Output
These are 2 examples in python
We can also separate the values dictionary and join them like shown below
x = 'These are %(number)d examples in %(name)s'
values = {'number': 2, 'name':'python'}
print (x % values)
#Output
These are 2 examples in python
5.2.5 Deep Copy vs Shallow Copy
Assignment statements in Python do not copy objects, they create bindings between a target and an object. In case of shallow copy, a reference of object is copied in other object. It means that any changes made to a copy of object do reflect in the original object. For collections that are mutable or contain mutable items, a copy is sometimes needed so one can change one copy without changing the other. This module provides generic shallow and deep copy operations.
The difference between shallow and deep copying is only relevant for compound objects (objects that contain other objects, like lists or class instances). A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original.
copy.copy(x)
Return a shallow copy of x.
Shallow copy of a list will not result in copying over the changes from the old list into the new one if you are just appending the original list; however, for nested objects the changes will be copied over. In essence, a shallow copy is only one level deep.
A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original.
copy.deepcopy(x)
Return a deep copy of x.
This creates a fully independent clone of the original object and all of its children.
import copy
# shallow copy example
old_l = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
new_l = copy.copy(old_l)
print (old_l)
print (new_l)
# shallow copy will not change wrt original list if old list is appended
old_l.append([11,1])
print (old_l)
print (new_l)
# however, the shallow copy does change if edits are made to nested object
old_l[1][1]=1110
print (old_l)
print (new_l)
# Output
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [11, 1]]
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
[[1, 2, 3], [4, 1110, 6], [7, 8, 9], [11, 1]]
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
5.2.6 Regular Expressions (Regex)
This is a vast topic better suitable to be discussed in a separate more detailed article, but let us go through a very simple regex use case.
More info on Regex
If you plan to reuse regex expression than you should first compile it into an object using re.compile()
import re
x = 'Regular expressions are really more efficient and flexible compared to str methods'
regex = re.compile('\s+')
print(regex.split(x))
# this is same as str.split method
print(" The result from x.split() is:\n", x.split())
#Output
['Regular', 'expressions', 'are', 'really', 'more', 'efficient', 'and', 'flexible', 'compared', 'to', 'str', 'methods']
The result from x.split() is:
['Regular', 'expressions', 'are', 'really', 'more', 'efficient', 'and', 'flexible', 'compared', 'to', 'str', 'methods']
5.3 Dictionary (Dict)
Dictionary or Dicts are associative arrays or hash maps which are initialized by curly brackets “{}” and colons are used to separate keys and values. Unlike sequences which are indexed by a range of numbers, dicts are indexed by keys which are immutable type such as strings or numbers or even tuples (if they only contain strings and numbers).
SampleDict={'a' : [1,2,3], 'b':[4,5]}
print (SampleDict)
print(SampleDict['a'])
#output
{'a': [1, 2, 3], 'b': [4, 5]}
[1, 2, 3]
Keep in mind that dictionaries do not maintain any sense of order for the input parameters; this is by design. This lack of ordering allows dictionaries to be implemented very efficiently, so that random element access is very fast, regardless of the size of the dictionary (if you’re curious how this works, read about the concept of a hash table). The python documentation has a complete list of the methods available for dictionaries.
5.3.1 zip()
You can zip()
two lists and pass it to a dict call to generate a dict. zip()
returns an iterable object in python 3.X, and hence we will have to wrap it in a list()
call to view the results. Apart from dicts and lists, zip()
can also be used to join together tuples.
L= zip(['a', 'b', 'c'], [1, 2, 3])
print (L)
L= list(zip(['a', 'b', 'c'], [1, 2, 3]))
print (L)
d= dict(L)
print (d)
T = tuple(zip(('a', 'b', 'c'), (1, 2, 3)))
print(T)
#Output
<zip object at 0x00000193BB360208>
[('a', 1), ('b', 2), ('c', 3)]
{'a': 1, 'b': 2, 'c': 3}
(('a', 1), ('b', 2), ('c', 3))
You can also unzip a list/dict/tuple into a tuple syntax below.
#unzipping a list
L1, L2 = zip(*L)
print(L1)
print(L2)
#zip(*zip(X, Y))
# unzipping a dict
keys, values = zip(*d.items())
print(keys)
print(values)
#Output
('a', 'b', 'c')
(1, 2, 3)
('a', 'b', 'c')
(1, 2, 3)
5.3.2 Dict Comprehensions
The same thing can be accomplished by dict comprehensions
D = {x: y for (x, y) in zip(['a', 'b', 'c'], [1, 2, 3])}
print (D)
#Output
{'a': 1, 'b': 2, 'c': 3}
Alternate way to create dicts from three different lists
L1= [1,2,3,4,5,]
L2= [6,7,8,9,10]
L3= ['a','b','c','d', 'e']
D = {}
for n in range (len(L3)):
D[L3[n]] = [L1[n],L2[n]]
# We can also use functions such as sum on lists opened by accesing dict's keys
sum(D[L3[0]])
print('The sum is %d and the D is' %x,D)
#Output
The sum is 15 and the D is {'a': [1, 6], 'b': [2, 7], 'c': [3, 8], 'd': [4, 9], 'e': [5, 10]}
Other dict methods:
D.get('key', returnvalue)
: if a given key is not found than it will return None as default; you can optionally specify return value.D.copy()
: Dict copy methodLen(D)
Adding values
D.update()
Deleting values
D.pop()
5.4 Tuples
Tuples are immutable data types which can be accessed by index number. Parentheses are not necassary but should be used for extra clarity. For initializing a tuple with no elements, ()
is used and for creating a tuple with one element, a trailing comma is used. The length of the tuple is given by len()
function.
SampleTuple=(1,2,3,4,5)
print (SampleTuple)
print (SampleTuple[2])
SampleOneTuple=8,
print (len(SampleOneTuple))
#Output
(1, 2, 3, 4, 5)
3
1
Tuple assignments can be performed by:
a,b = 1,2+1
# is same as tuple
# (a,b) = (1,2+1)
print (a)
print (b)
# and this ASSIGNS
# a = c and b = d+e
# Output
1
3
5.5 Sets
Set is an unordered collection of unique elements, like a dict but with keys ony. It can be created by either curly braces or by set()
function.
testset=set([2, 2, 2, 1, 1, 3, 3])
print (testset)
{1, 2, 3}
5.6 More Specialized Data Structures
Python contains several other data structures that you might find useful; these can generally be found in the built-in collections
module.
The collections module is fully-documented in Python’s online documentation, and you can read more about the various objects available there.
In particular, I’ve found the following very useful on occasion:
collections.namedtuple
: Like a tuple, but each value has a namecollections.defaultdict
: Like a dictionary, but unspecified keys have a user-specified default valuecollections.OrderedDict
: Like a dictionary, but the order of keys is maintained
Once you’ve seen the standard built-in collection types, the use of these extended functionalities is very intuitive, and I’d suggest reading about their use.
6.0 Operators
These are class of special symbols that allow you to make arthmetic assignments, boolean operations, assign variables etc. The symbols itself are not special at all; it is your “normal” mathematical symbols such as =,+,-, etc., Python’s syntax is intuituve enough that you will be able to use it without much discussion; hence we will write them up in table form.
6.1 Arithmetic
Operator | Name | Description |
---|---|---|
a + b |
Addition | Sum of a and b |
a - b |
Subtraction | Difference of a and b |
a * b |
Multiplication | Product of a and b |
a / b |
True division | Quotient of a and b |
a // b |
Floor division | Quotient of a and b , removing fractional parts |
a % b |
Modulus | Integer remainder after division of a by b |
a ** b |
Exponentiation | a raised to the power of b |
-a |
Negation | The negative of a |
+a |
Unary plus | a unchanged (rarely used) |
6.2 Comparison
Operation | Description |
---|---|
a == b |
a equal to b |
a < b |
a less than b |
a <= b |
a less than or equal to b |
a != b |
a not equal to b |
a > b |
a greater than b |
a >= b |
a greater than or equal to b |
6.3 Identity and Membership
Operator | Description |
---|---|
a is b |
True if a and b are identical objects |
a is not b |
True if a and b are not identical objects |
a in b |
True if a is a member of b |
a not in b |
True if a is not a member of b |
6.4 Bitwise Operators
Operator | Name | Description |
---|---|---|
a & b |
Bitwise AND | Bits defined in both a and b |
a | b |
Bitwise OR | Bits defined in a or b or both |
a ^ b |
Bitwise XOR | Bits defined in a or b but not both |
a << b |
Bit shift left | Shift bits of a left by b units |
a >> b |
Bit shift right | Shift bits of a right by b units |
~a |
Bitwise NOT | Bitwise negation of a |
7.0 Decision Statements
Like most programming languages, Python contains if-else based decision statements.
if "data" in text:
print(text)
else:
print("no")
We can use elif for testing multiple conditions.
x = 15
if x == 0:
print(x, "is zero")
elif x > 0:
print(x, "is +ve")
elif x < 0:
print(x, "is -ve")
else:
print(x, "some other error")
#Output
15 is +ve
We can evaluate nonboolean types as Booleans intuitively. For example, if response is a string entered by a user, and we want to condition a behavior on this being a nonempty string, we may write
if response:
# as a shorthand for the equivalent,
if response != :
8.0 Loops
There are two types of loops available in Python, the while
loop and the for
loop.
while True:
pass
We can use the break
is used for exiting nearest enclosing loop and continue
is used to jump to top of nearest enclosing loop
while True:
if exittest():
break
while True:
if skiptest():
continue
The for
loop is more commonly used and you can combine it with the range
function to iterate through a predetermined number of iterations.
for i in range(5):
print(i, end=' ')
# Output:
0 1 2 3 4
The loop-else combination is kind of like a nobreak statement: the else block is executed only if the loop ends on its own, without encountering a break statement; this can be perfectly illustrated using a for
loop.
L = []
nmax = 10
for n in range(2, nmax):
for factor in L:
if n % factor == 0:
break
else: # no break
L.append(n)
print(L)
# Output
[2, 3, 5, 7]
The range() function can be used to generate a list of sequences and has two sets of parameters.
range(stop): Number of integers (whole numbers) to generate, starting from zero. eg. range(3) == [0, 1, 2].
range([start], stop[, step]), start: Starting number of the sequence. stop: Generate numbers up to, but not including this number. step: Difference between each number in the sequence.
8.1 Revisting List Comprehensions
List comprehensions are simply a way to compress a list-building for-loop into a single short, readable line. For example, here is a loop that constructs a list of the first 12 square integers:
L = []
for n in range(12):
L.append(n ** 2)
print(L)
#Output
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]
The list comprehension equivalent of this is the following:
[n ** 2 for n in range(12)]
#Output
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]
8.2 List Generator Expressions
Representative generator expression is shown below. Note that the syntax is similar to list comprehensions except that it uses parentheses instead of square brackets used by list comprehensions.
(n ** 2 for n in range(12))
#Output:
<generator object <genexpr> at 0x00000246B36FAE08>
Notice that printing the generator expression does not print the contents; one way to print the contents of a generator expression is to pass it to the list
constructor:
G = (n ** 2 for n in range(12))
list(G)
#Output
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]
8.3 enumerate
Often you need to iterate not only the values in an array, but also keep track of the index. You might be tempted to do things this way:
L = [2, 4, 6, 8, 10]
for i in range(len(L)):
print(i, L[i])
#Output
0 2
1 4
2 6
3 8
4 10
Although this does work, Python provides a cleaner syntax using the enumerate
iterator:
for i, val in enumerate(L):
print(i, val)
#Output
0 2
1 4
2 6
3 8
4 10
9.0 Functions
Functions in Python are simply defined by def
and return
keyword as shown below. It takes in three values a,b and c and simply returns the sum. Notice how we have supplied a default value for c but nothing for a and b.
def f(a, b, c=1):
x = a+b+c
return (x)
print(f (0,b=0))
#Output
1
I think you must have noticed how we can specify function arguments based on position or keyword. If you want to force keyword assignments rather than positional assignments, the syntax is
def f(*, a, b, c=1):
x = a+b+c
return (x)
f (a=0,b=0)
#Output
1
keyword-only arguments are coded as named arguments that appear after *args in the arguments list. All such arguments must be passed using keyword syntax. For example, in the following, a may be passed by name or position, x collects any extra positional arguments, and b, c must be passed by keyword only
def f(a, *x, b, c=1):
x = a+b+c
return (x)
f (0,b=0,c=1)
Note: Function do not require a return statement; you can choose to write a function which simply prints a value on the console; however, for most cases, it will be a good idea to always include a return statement.
9.1 *args
and **kwargs
: Flexible Arguments
Sometimes you might wish to write a function in which you don’t initially know how many arguments the user will pass.
In this case, you can use the special form *args
and **kwargs
to catch all arguments that are passed.
Here is an example:
def catch_all(*args, **kwargs):
print("args =", args)
print("kwargs = ", kwargs)
catch_all(1, 2, 3, a=4, b=5)
catch_all('a', keyword=2)
# Output
args = (1, 2, 3)
kwargs = {'a': 4, 'b': 5}
args = ('a',)
kwargs = {'keyword': 2}
Here it is not the names args and kwargs that are important, but the * characters preceding them. args and kwargs are just the variable names often used by convention, short for “arguments” and “keyword arguments”. The operative difference is the asterisk characters: a single * before a variable means “expand this as a tuple”, while a double ** before a variable means “dictionnary of named arguments”. In fact, this syntax can be used not only with the function definition, but with the function call as well!
inputs = (1, 2, 3)
keywords = {'pi': 3.14}
catch_all(*inputs, **keywords)
#Output
args = (1, 2, 3)
kwargs = {'pi': 3.14}
*args and ** kwargs can be used with named arguments too. The priority of evaluation is that explicit arguments get values first and then everything else is passed to *args and than to **kwargs.
In the example below, you can see that we since a, b were named arguments but only a was passed explicitly, so the value 2 from the tuple *args was evaluated as being b.
def foo(a, b, *args, **kwargs):
print (a, b, args, kwargs)
foo(1, *(2,3,4), **{'5':5})
#Output
1 2 (3, 4) {'5': 5}
9.2 Lambda Functions
Earlier we quickly covered the most common way of defining functions, the def
statement.
You’ll likely come across another way of defining short, one-off functions with the lambda
statement.
It looks something like this:
add = lambda x, y: x + y
add(1, 2)
# Output
3
This lambda function is roughly equivalent to
def add(x, y):
return x + y
9.3 Scopes and Namespaces
If a variable is assigned inside a def statement then it is local to that function. If a variable is assigned in an enclosing def statement then it is nonlocal to nested functions. If a variable is assigned outside all def statements then it is global to the entire file.
You can assign a variable with global scope by using global
statement; in the example shown below the value of x is dependent on when the function is called.
x = 'old'
def function():
global x
x = 'new'
print (x)
function()
print (x)
#Output
old
new
nonlocal
variable must only be defined in local nested function otherwise it gives an error.
def f():
nonlocal x
print(x)
x = "old"
f()
#Output
File "<ipython-input-3-a60803635d03>", line 2
nonlocal x
^
SyntaxError: no binding for nonlocal 'x' found
However, this same function works fine when we change x into global variable.
def f():
global x
print(x)
x = "old"
f()
#Output
old
9.4 Decorators
Decorators are functions returning another function, usually applied as a function transformation using the @wrapper syntax. So they accept functions or methods as arguments, and return a new “decorated” function/method. We will not discuss it here since its really an advanced topic but you are encouraged to check it out in the official Python documentation.
10.0 Catching Errors and Exceptions
You can catch errors and exceptions in Python using the try-except-else-finally code blocks as shown below. Note: the only else and finally statements are not necassary and you can use none, either or both of it based on your need.
The code present in try block is executed first; if it fails than the except clause is executed. else statements are executed if try block code succeeds and finally statement is always executed no matter if try block succeeds or fails.
try:
print("try dividing by zero")
100/0
except Exception as E:
print("this happens when try block fails, the exception raised is:")
print(str(E))
else:
print("this happens only if try block suceeds")
finally:
print("this always gets executed")
#Output
try dividing by zero
this happens when try block fails, the exception raised is:
division by zero
this always gets executed