Skip to content

Instantly share code, notes, and snippets.

@nlharri
Last active June 2, 2019 20:05
Show Gist options
  • Save nlharri/4eb2522fa0a80eeaf4f1b50baecaefa2 to your computer and use it in GitHub Desktop.
Save nlharri/4eb2522fa0a80eeaf4f1b50baecaefa2 to your computer and use it in GitHub Desktop.
Notes taken during learning Python

Notes taken during learning Python

These notes were taken during learning Python programming language.

Learning resources:

Topics to Learn

  • Algorithms and data structures
  • Object Oriented Python, Classes, Objects, Inheritance, Method Overloading, Operator Overloading, ...
  • Decorators
  • Generators
  • Iterators
  • Closures
  • Lambda functions / Anonymous functions
  • Modules in Python
  • Variable length arguments
  • Pass a parameter to a function
  • Types of arguments, function arguments
  • Exception handling
  • Multithreading
  • UI: Tkinter
  • PostgreSQL
  • Desktop app, SQLite
  • OpenCV, image processing
  • Bokeh: data visualization
  • Webscraping: BeautifulSoup
  • Tensorflow
  • Keras
  • Flask
  • Django

Algorithms and Data Structures

  • Complexity, ordo, etc...
  • Searching, Sorting
  • Arithmetic with big numbers
  • Data Structure: Linked List
  • Data Structure: Binary Search Tree
  • Data Structure: AVL Tree
  • Data Structure: Red-Black Tree
  • Data Structure: 2-3 Tree
  • Data Structure: B-Tree
  • Data Structure: Queue
  • Data Structure: Stack
  • Data Structure: Graph
  • Graph Algorithm: Breadth-first-search
  • Graph Algorithm: Depth-first search
  • Graph Algorithm: Dijkstra's Shortest Path First algorithm, SPF algorithm
  • Graph Algorithm: Floyd–Warshall-algorithm
  • Graph Algorithm: Johnson-algorithm
  • Graph Algorithm: Suurballe-algorithm
  • Graph Algorithm: Graph Coloring - Two Colors of Vertices
  • Graph Algorithm: The Minimum Spanning Tree of an Undirected Graph
  • Graph Algorithm: PERT method - Topologic Sequence - House Building
  • Graph Algorithm: Acyclic Dijkstra
  • Graph Algorithm: Bellman–Ford-algorithm
  • Graph Algorithm: Matching, Stable Matching, Gale–Shapley algorithm
  • Graph Algorithm: Maximal Matching in Bipartite Graphs ("páros gráf" = "bipartite graph"), Hungarian algorithm
  • Graph Algorithm: Matching in non-bipartite graphs
  • Graph Algorithm: Matching - Hopcroft–Karp-algorithm
  • Graph Alhorithm: Network flows - Maximal flow - Ford–Fulkerson algorithm, Edmonds–Karp algorithm
  • Graph Alhorithm: Network flows - Dinic-algorithm
  • Hashing
  • Data Compression, Huffmann
  • Arithmetics, multiplication of big numbers with Karacuba method
  • Arithmetics, multiplication of big numbers with Schönhage-Strassen method
  • Discrete Fourier Transformation, FFT
  • Dynamic programming: Optimal binary search tree

Machine Learning Roadmap

Python Language Elements

These are some specialities in Python which were not so common in the programming languages I used so far (eg. ABAP).

or and and in expressions, short-circuit evaluation

How does it work:

  • or returns the first that is truthy
  • and returns the first that is falsy

Example function:

def f(arg):
  print("-> f({})".format(arg))
  return arg

Tests:

>>> f(0) or f(False) or f(1) or f(2) or f(3)
-> f(0)
-> f(False)
-> f(1)
1

>>> f(0) and f(False) and f(1) and f(2) and f(3)
-> f(0)
0

>>> f(1) and f(2) and f(3) and f(0) and f(False) and f(True)
-> f(1)
-> f(2)
-> f(3)
-> f(0)
0

>>> f(0) or f(False) or f(0.0)
-> f(0)
-> f(False)
-> f(0.0)
0.0

>>> f(0) and f(False) and f(0.0)
-> f(0)
0

Good examples

>>> string = 'foo bar'
>>> s = string or '<default_value>'
>>> s
'foo bar'
>>> string = ''
>>> s = string or '<default_value>'
>>> s
'<default_value>'

Decorators

def div(a,b):
    print(a/b)


def smart_div(func):

    def inner(a,b):
        if a < b:
            a,b = b,a
        return(func(a,b))

    return inner

div1 = smart_div(div) # or we can even name the function div also

div1(2,4)

Alternative way of using the decorator: with @ symbol

def smart_div(func):

    def inner(a,b):
        if a < b:
            a,b = b,a
        return(func(a,b))

    return inner

@smart_div
def div(a,b):
    print(a/b)

div(2,4)

Modules in Python

# This file is named BasicOperations.py and is in folder Calculation
def add(a, b):
    return a+b


def sub(a, b):
    return a-b


def mul(a, b):
    return a*b


def div(a, b):
    return a/b
# This file is named demo.py and is in the root folder of the project
import Calculation.BasicOperations
# alternative way:
# import Calculation.BasicOperations as BasicOps

a = 7
b = 9

c = Calculation.BasicOperations.add(a, b)
d = Calculation.BasicOperations.sub(a, b)
e = Calculation.BasicOperations.mul(a, b)
f = Calculation.BasicOperations.div(a, b)

def main():
    print(c)
    print(d)
    print(e)
    print(f)

if __name__ == "__main__":
    main()

OOP in Python

Passing arguments in Python

If you pass immutable arguments like integers, strings or tuples to a function, the passing acts like call-by-value. The object reference is passed to the function parameters. They can't be changed within the function, because they can't be changed at all, i.e. they are immutable. It's different, if we pass mutable arguments. They are also passed by object reference, but they can be changed in place in the function. If we pass a list to a function, we have to consider two cases: Elements of a list can be changed in place, i.e. the list will be changed even in the caller's scope. If a new list is assigned to the name, the old list will not be affected, i.e. the list in the caller's scope will remain untouched.

Handling of immutable arguments

Immutable types are the following:

  • bool
  • int
  • float
  • str
  • tuple
  • frozenset

These are handled like pass-by-value. The object reference is passed to the function parameters. They can't be changed within the function, because they can't be changed at all, i.e. they are immutable.

It is also important to mention that Python initially behaves like call-by-reference, but as soon as we are changing the value of such a variable, i.e. as soon as we assign a new object to it, Python "switches" to call-by-value. This means that a local variable x will be created and the value of the global variable x will be copied into it.

Consider the following example:

def ref_demo(x):
    print("x and id(x) inside the function, before changing it ", x, id(x))
    x = 5000
    print("x and id(x) inside the function, after changing it ", x, id(x))

if __name__ == "__main__":
    x = 500
    print("x and id(x) outside the function, before calling the function ", x, id(x))
    ref_demo(x)
    print("x and id(x) outside the function, after calling the function ", x, id(x))

The output of the above is

x and id(x) outside the function, before calling the function  500 4402783344
x and id(x) inside the function, before changing it  500 4402783344
x and id(x) inside the function, after changing it  5000 4401954768
x and id(x) outside the function, after calling the function  500 4402783344

Handling of mutable arguments

Mutable types are the following:

  • list
  • array
  • set
  • dict

These are handled like pass-by-reference. They are also passed by object reference, but they can be changed in place in the function.

Handling of list passed to a function

If we pass a list to a function, we have to consider two cases: Elements of a list can be changed in place, i.e. the list will be changed even in the caller's scope. If a new list is assigned to the name, the old list will not be affected, i.e. the list in the caller's scope will remain untouched.

No Side Effects

Consider the following example:

def no_side_effects(cities):
    print('locations inside method call, before modification', cities)
    cities = cities + ["Birmingham", "Bradford"]
    print('locations inside method call, after modification', cities)

print("test with \"cities = cities + ['..', '..']\" - no side effects")

locations = ["London", "Leeds", "Glasgow", "Sheffield"]

print('locations before method call', locations)

no_side_effects(locations)

print('locations after method call', locations)

Output:

test with "cities = cities + ['..', '..']" - no side effects
locations before method call ['London', 'Leeds', 'Glasgow', 'Sheffield']
locations inside method call, before modification ['London', 'Leeds', 'Glasgow', 'Sheffield']
locations inside method call, after modification ['London', 'Leeds', 'Glasgow', 'Sheffield', 'Birmingham', 'Bradford']
locations after method call ['London', 'Leeds', 'Glasgow', 'Sheffield']

Side Effects

Consider the following example:

def side_effects(cities):
    print('locations inside method call, before modification', cities)
    cities += ["Birmingham", "Bradford"]
    print('locations inside method call, after modification', cities)

print("test with \"cities += ['..', '..']\" - side effects, because += works as in-place operator")

locations = ["London", "Leeds", "Glasgow", "Sheffield"]

print('locations before method call', locations)

side_effects(locations)

print('locations after method call', locations)

Output:

test with "cities += ['..', '..']" - side effects, because += works as in-place operator
locations before method call ['London', 'Leeds', 'Glasgow', 'Sheffield']
locations inside method call, before modification ['London', 'Leeds', 'Glasgow', 'Sheffield']
locations inside method call, after modification ['London', 'Leeds', 'Glasgow', 'Sheffield', 'Birmingham', 'Bradford']
locations after method call ['London', 'Leeds', 'Glasgow', 'Sheffield', 'Birmingham', 'Bradford']

Avoid Side Effects: Shallow Copy

A shallow copy is sufficient, because there are no nested structures in the list.

def side_effects(cities):
    print('locations inside method call, before modification', cities)
    cities += ["Birmingham", "Bradford"]
    print('locations inside method call, after modification', cities)

print("test with \"cities += ['..', '..']\" - side effects, because += works as in-place operator")

locations = ["London", "Leeds", "Glasgow", "Sheffield"]

print('locations before method call', locations)

side_effects(locations[:]) # shallow copy

print('locations after method call', locations)

Output:

test with "cities += ['..', '..']" - side effects, because += works as in-place operator
locations before method call ['London', 'Leeds', 'Glasgow', 'Sheffield']
locations inside method call, before modification ['London', 'Leeds', 'Glasgow', 'Sheffield']
locations inside method call, after modification ['London', 'Leeds', 'Glasgow', 'Sheffield', 'Birmingham', 'Bradford']
locations after method call ['London', 'Leeds', 'Glasgow', 'Sheffield']

Variable length arguments

def varpafu(*x): 
    print('parameters: ', x)
    print('type of parameters: ', type(x))

if __name__ == "__main__":
    varpafu(12, 'Second text parameter', "Third param", False, 2.43, ["a","b"], ("c","d",34,"e"))
parameters:  (12, 'Second text parameter', 'Third param', False, 2.43, ['a', 'b'], ('c', 'd', 34, 'e'))
type of parameters:  <class 'tuple'>

The asterisk "*" is used in Python to define a variable number of arguments. The asterisk character has to precede a variable identifier in the parameter list.

Sometimes, it's necessary to use positional parameters followed by an arbitrary number of parameters in a function definition. This is possible, but the positional parameters always have to precede the arbitrary parameters. In the following example, we have a positional parameter "city", - the main location, - which always have to be given, followed by an arbitrary number of other locations:

def locations(city, *other_cities): print(city, other_cities)

locations("Paris")

locations("Paris", "Strasbourg", "Lyon", "Dijon", "Bordeaux", "Marseille")
Paris ()
Paris ('Strasbourg', 'Lyon', 'Dijon', 'Bordeaux', 'Marseille')

Arbitrary number of keyword parameters

There is also a mechanism for an arbitrary number of keyword parameters. To do this, we use the double asterisk "**" notation:

def varpafu(**kwargs): 
    print('parameters: ', kwargs)
    print('type of parameters: ', type(kwargs))

if __name__ == "__main__":
    varpafu(a=12, b='Second text parameter', c="Third param", d=False, e=2.43, f=["a","b"], g=("c","d",34,"e"))
parameters:  {'a': 12, 'b': 'Second text parameter', 'c': 'Third param', 'd': False, 'e': 2.43, 'f': ['a', 'b'], 'g': ('c', 'd', 34, 'e')}
type of parameters:  <class 'dict'>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment