Class 1: More Syntax and The Python Stack#

Review#

  • Describe the general function of the Python interpreter in a phrase or two.

Solution

The interpreter transpiles the Python code by translating it line-after-line into C code and then compiling into binary code.

  • Describe the significance of whitespace at the beginning of lines to the Python interpreter, if any.

Solution

Whitespace in the beginning of lines indicates the scope* of the code, and it is therefore crucial to the function of the interpreter. A new indentation level is declared with :, and while any addition of whitespace will work, the standard is to use four spaces.

Note

  • The word scope is used here in a somewhat loose (as in wrong) way, however, it is a useful (and common) “mistake” that I will probably sometimes make throughout this course. Therefore, it is important for me to make the distinction and clarify that in the context of Python’s syntax, people (me included), often colloquially refer to the expected indentation level of the code as the “scope”.

  • What would be the output of the following code:

    str(((11 % 6) // 2 < 3 < 3e2) == 1)
    
Solution
'True'

Conditionals and Iteration#

Even though you’ve learned it by yourself in the first class and while doing homework, we’ll go through these subjects (quickly) one more time, for the sake of completeness.

The if Statement#

Minimal example:

x = 10
if x > 10:
    print("x is bigger than 10")

Using else:

y = 11
if x > y:
    print("x")
else:
    print("y")
y

Multiple conditions (elif):

z = 12
if x > y:
    print("x")
elif x > z:
    print("x > z")
elif z < y:
    print("z is small")
    if z < x:
        print("wow, z IS small")

Iteration#

One of Python’s strongest features.

for loop#

We’ll compare a naive script adding up numbers in an array using MATLAB and Python:

% Sum all items in array
data = [10, 20, 30];
result = 0;
for idx = 1:size(data, 1)
    result = result + data(idx);
end

In Python we can iterate over the values themselves using the in operator:

data = [10, 20, 30]
result = 0
for value in data:
    print(value)
    result += value

print("The result is:", result)
10
20
30
The result is: 60

Note

Newer versions of MATLAB technically enable iterating values, but it’s still very clunky (row vs. column vectors) and doesn’t work in some important use-cases (e.g. parfor).

We can iterate over nearly anything:

tup = (1, 2, True, 3.0, 'four')
for item in tup:
    print(item)
1
2
True
3.0
four
string = "abcdef"
for letter in string[::-2]: # Strings are also sliceable!
    print(letter)
f
d
b

To get both the index and the value of some sequence, use the enumerate keyword:

my_tuple = 1, 2, True, 3.0, 'four'
for index, item in enumerate(my_tuple):
    print(index, item)
    
0 1
1 2
2 True
3 3.0
4 four

By default, iterating a dictionary returns it’s keys:

print('Key iteration:')
dict1 = {'a': 1, 'b': 2, 'c': 3}
for key in dict1:
    print(key)
Key iteration:
a
b
c

However, iterating a dictionary’s values or items (key and value pairs represented as tuples) is just as easy:

print('Value iteration:')
for val in dict1.values():
    print(val)

print('Pairs iteration:')
for key, val in dict1.items():
    print(key, val)
Value iteration:
1
2
3
Pairs iteration:
a 1
b 2
c 3

while Loop#

def countdown(n):
    """ Explodes a bomb when n is zero. """
    while n > 0:
        print("{}...".format(n))
        n = n - 1
    print("BOOM!")
    return True
n = 10
val = countdown(n)

print(val)
10...
9...
8...
7...
6...
5...
4...
3...
2...
1...
BOOM!
True

Note

Generally speaking, the usage of while loops in Python is discouraged. Whenver you’re tempted to use one, consider the possibility of replacing it with a for loop. If at all possible, this solution is probably preferable.

break and continue#

Stopping a loop is done with break, and in order to continue execution from the start of the loop we use continue:

data = [1., 2., 1., 1., 4., 1.]
for datum in data:
    if datum == 2.:
        continue
    if datum != 1.:
        print(datum)
        break
    print("Still 1...")
Still 1...
Still 1...
Still 1...
4.0

Conditionals and Iteration Exercise

Write a function that receives a list of numbers and returns the first number that is larger than the sum of its two neighbors (with the first and the last numbers counting as each other’s neighbors), or None if no such number is found.

Solution
def find_first_outlier(numbers: list):
    """
    Returns the first number greater than the sum of its neighbors, or None.

    Parameters
    ----------
    numbers : list
        List of numbers

    Returns
    -------
    Union[int, float, None]
        First outlier number, or None
    """

    size = len(numbers)
    for i, number in enumerate(numbers):
        previous_number = numbers[i - 1]
        next_number = numbers[(i + 1) % size]
        neighbors_sum = previous_number + next_number
        if number > neighbors_sum:
            return number

Early stopping

Note how the function’s return statement can be used similarly to break in order to stop the iteration once a match is found (in cases where returning the given match is the purpose of the function). This implementation is identical to:

def find_first_outlier(numbers: list):
    """
    Returns the first number greater than the sum of its neighbors, or None.

    Parameters
    ----------
    numbers : list
        List of numbers

    Returns
    -------
    Union[int, float, None]
        First outlier number, or None
    """

    size = len(numbers)
    match = None
    for i, number in enumerate(numbers):
        previous_number = numbers[i - 1]
        next_number = numbers[(i + 1) % size]
        neighbors_sum = previous_number + next_number
        if number > neighbors_sum:
            match = number
            break
    return match

Formatting#

We can print together variables and text in several different manners:

# Not recommended as it doesn't allow for customizations
a = 42
print("The value of a is", a)
The value of a is 42
# Older version, similar to other languages
a = 42
b = 32
print("The value of a is %d, while the value of b is %d" % (a, b))
The value of a is 42, while the value of b is 32
# A decent option
a = 42
b = 32
print("The value of a is {}, while the value of b is {}".format(a, b))
The value of a is 42, while the value of b is 32
# Only for Python 3.6+ - but it's pretty cool. It's called f-strings
a = 42
b = 32
print(f"The value of a is {a}, while the value of b is {b}")
The value of a is 42, while the value of b is 32
# Another example
a = 42
b = 32
print(f"The value of a is {a:.2f}, while the value of b is not {b + 1}.")
# You can write any expression you'd like inside the curly brackets.
The value of a is 42.00, while the value of b is not 33.

Throughout the course you’ll see me using mostly the f-string option, which is the most readable assuming you only work on Python 3.6+.

Comprehensions#

Comprehensions are a set of fast and delightfully readable methods for creating different types of iterables.

Assume we wish to create a list with the squared values of the numbers in the range [0, 10). Instead of the old:

squares = []
for item in range(10):
    squares.append(item ** 2)
    
print(squares)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

we could use a list comprehension:

squares = [x ** 2 for x in range(10)]

print(squares)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

This amazing piece of software iterates of the range with the variable x, and places its square inside a list, which is then allocated to the squares variable.

What other goodies do comprehensions allow? Filtering.

squares = [
    x ** 2 
    for x in range(10) 
    if x != 8
]

print(squares)
[0, 1, 4, 9, 16, 25, 36, 49, 81]

The if statement is evaluated on each iteration of x. Notice how similar to English this expression is?

Funnily enough, performance isn’t hindered:

%timeit squares = [x ** 2 for x in range(100) if x != 8]
23.5 μs ± 399 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
%%timeit
squares = []
for item in range(100):
    if item != 8:
        squares.append(item ** 2)
25.8 μs ± 17.6 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

Another example:

strings = [cons.upper()
           for cons in 'abcdefghij' 
           if cons not in 'aeiou']

print(strings)
['B', 'C', 'D', 'F', 'G', 'H', 'J']

Comprehensions aren’t limited to lists! You can also comprehend dictionaries, tuples and sets. To demonstrate a dict comprehension easily we will usethe built-in zip function:

keys = 'abcde'
vals = [1, 2, 3, 4, 5]
bools = [True, True, False]

# Zip bundles up these iterables together, allowing iteration
for a, b, c in zip(keys, vals, bools):
    print(a, b, c)
a 1 True
b 2 True
c 3 False

With zip we can comfortably bundle up the key-value pairs:

keys = 'abcde'
values = [1, 2, 3, 4, 5]
d = {key: value 
     for key, value in zip(keys, values) 
     if key != 'c'}

print(d)
{'a': 1, 'b': 2, 'd': 4, 'e': 5}

If we don’t need the “if” part of the comprehension we can even:

d = dict(zip(keys, values))
print(d)
{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}

The same principle applies for set comprehensions:

se = {number % 7 for number in range(100)}

print(se)
{0, 1, 2, 3, 4, 5, 6}

Lastly, list comprehensions can iterate over more than one iterable:

summed_list = [outer + inner 
               for outer in range(10) 
               for inner in range(10, 20)]

This is identical to:

summed_list = []
for outer in range(10):
    for inner in range(10, 20):
        summed_list.append(outer + inner)

Once you get used to comprehensions, they quickly become the most “natural” way to iterate. Their brevity on the one hand, and readability on the other, are their most important features. That’s why comprehension are very encouraged when writing true “Pythonic” code.

Function Default Arguments and Types#

One thing we didn’t show in our introduction to functions was the default argument feature of function definitions, as well as the optional typing syntax. We’ll start with showing what are default arguments.

Say you have a function which calculates the nth-power of any number its given. It obviously has to be given two inputs - the number and the power by which it will be multiplied. For example:

def calculate_power(x, power):
    """ Raises *x* to the power of *power* """
    return x ** power

This function works wonderfully, but we’re assuming that most of the times users will raise x to the power of two. If we want to save the hassle for the caller, we can use default arguments when defining the function:

def calculate_power(x, power=2):
    """ Raises *x* to the power of *power* """
    return x ** power

We can now call this function either with the power argument or without it, which will default it to 2:

print(calculate_power(10))
print(calculate_power(10, 3))
100
1000

Type hinting#

Another interesting feature of Python (3.6+) is its optional typing capabilities. These allow us coders to ‘hint’ at the wanted types of certain variables. We say ‘hint’ since these annotations have no effect during runtime, i.e. a variable can be marked as a string but later receive an integer value without the Python interpreter caring. For functions, Python added a special “arrow” -> sign that marks the output type of it. Let’s see how it works:

def typed_add(a: int, b: int = 0) -> int:  # combination of type hinting and default arguments
    """Adds the two inputs together"""
    return a + b
a: float = 0.25
b: str = 'a'

a = True  # Python doesn't care that we've overriden the type hint

Type hinting is used for documentation purposes mostly. Later in the course we’ll discuss its more advanced features by utilizing the mypy package.

Functions and Comprehensions Exercise

Write a function that receives an iterable of full subject names (first name and then last name) and returns a dictionary of nested dictionaries, wherein each key is in the format “sub-i” and each value is a dictionary containing the keys "first_name" and "last_name", as well as the corresponding values.

For example:

>>> subjects = ["Freddie Mercury", "Robert Plant"]
>>> parse_subject_names(subjects)
{'sub-0': {'first_name': 'Freddie', 'last_name': 'Mercury'},
 'sub-1': {'first_name': 'Robert', 'last_name': 'Plant'}}
Solution
from typing import Dict, Iterable

def parse_subject_names(names: Iterable[str]) -> Dict[str, Dict[str, str]]:
    name_keys = "first_name", "last_name"
    return {
        f"sub-{i}": dict(zip(name_keys, name.split()))
        for i, name in enumerate(names)
    }

Advanced type-hinting

Python’s built-in typing module enables the specification of even more detailed and refined type-hinting.

File Input/Output#

Yet another error-prone area in applications is their I/O (input-output) module. Interfacing with objects outside the scope of your own project should always be handled carefully. You never know what’s really out there.

Assume we wish to write some data to a file - a list filled with counts of some sort, for example.

To write (and read) from a file, you have to do several operations:

  1. Define the file path and name.

  2. Open the file with the appropriate mode - read, write, etc.

  3. Flush out the data.

  4. Close the file.

Here’s a mediocre example of how it’s done:

data_to_write = 'A B C D E F'
filename = 'data.txt'
file = open(filename, 'w')  # w is write, 'open' is a built-in function
file.write(data_to_write)
file.close()

The variable file is a file object, and it has many useful methods, such as:

  • .read() - reads the entire file.

  • .readline() - reads a single line.

  • .readlines() - read the entire file as strings into a list.

  • .seek(offset) - go the offset position in the file.

File objects in Python can be opened as string files (the default) or as binary files (open(filename, 'b')), in which case their content will be interpreted as bytes rather than text.

When dealing with files, we generally first open() them, read() \ write() something, and close() them. The real issue stems from the fact that these steps are very error prone. For example, you can open a file to write something to it, but while the file is opened someone else (or some other Python process) can close and even delete the file.

Another example - some connection error might occur after you’ve flushed the data into the file, but before you managed to close it, leading to a file that can’t be accessed by the operating system.

Gladly, Python is here to help, and its main method of doing so is context managers, called upon with the with keyword. Context managers are awesome, and I’ll only briefly describe their capabilities. That being said, they shine the most when doing I/O, like in the following example:

data_to_write = 'A B C D E F'
filename = 'data.txt'
with open(filename, 'w') as file:
    file.write(data_to_write)
    file.write('abc')

The unique thing here is that once we’ve opened the file, the with block guarantees that the file will be closed, regardless of what code is executed.

Even if an error occurs while the file is open - the context manager will ensure proper handling of the file and prevent our data from disappearing into the void of the file system.

The Python Stack#

How to Run Python?#

How does MATLAB do it?#

MATLAB has its excellent application GUI which essentially everyone uses all the time.

But all interpreted programming languages, including Python and MATLAB, can be run from the command line.

In the case of MATLAB, though, rarely do you see people running it from the command line:

MATLAB from the CL

It’s obviously possible, but less comfortable than the standard GUI we all know. If all you wish to do is run a MATLAB .m file, you can also do it from the command line by simply writing matlab -r myfile.m.

The MATLAB application we’re familiar with combines a few sub-applications:

  1. Text editor

  2. Debugger and variable explorer

  3. Command prompt (REPL, a place to write and immediately evaluate MATLAB expressions)

  4. MATLAB’s engine or interpreter (the program that actually does the job of reading the source files and ‘computing’ them)

Python is very similar. Just like MATLAB, the quickest option to run Python is from the command line, by simply writing python:

Python CL

Running Python scripts, like myfile.py, is as easy as python myfile.py.

Integrated Development Environment (IDE)#

More often than not, we wish to both write a script, experiment with it a little, and then run it, just as some of us are used to do from the MATLAB environment. Python, being non-propietary, has several such solutions. Generally speaking, software of this type are referred to as Integrated Development Enviornments (IDEs), and are a basic tool every programmer uses, with choice varying greatly depending on the languages used as well personal preference.

Some popular options for Python are:

Spyder#

Spyder is an open-source, science-oriented IDE, that was designed with MATLAB’s GUI in mind. It contains many similar functions and might look very similar in a quick glance:

Spyder IDE

Unfortunately, Spyder’s main financial support was cut off in November 2017. It’s still in active development, but on a much slower pace, and its future is unclear at the moment.

PyCharm#

PyCharm is a full-blown IDE which contains many advanced features that any modern IDE has, like refactoring capabilities, testing suites and more. It has a free community edition, and a paid proffessional edition, which is actually free for poor students like us:

PyCharm IDE

Visual Studio Code#

VSCode is a free, open-source editor for nearly all existing programming languages. It’s relatively lightweight, and relies mostly on its community-driven extensions marketplace. There’s an extension for just about anything you might want to do with a code editor, and both the editor itself and its popular extensions are well maintained and in active development.

VS Code

Jupyter Notebook#

While not technically an IDE, Jupyter is designed with data exploration in mind. It’s less suited for writing long, complex application, but great when it comes for a quick “plot-n-go” on some data you recently acquired.

Version Control#

Introduction#

Version control is the active management of the history of your source code. It is an essential part of every developers’ work cycle, for both small and large projects.

With version control, in any point in time during the work on your code you can decide to “commit” the change. Committing your code means that the system will remember the current state of your work (all files in a folder), and will allow you to return to this exact state of your codebase whenever you wish.

Final.doc, from PhD Comics

Using version control is orthogonal to the traditional save operation. Saving records the current state of your codebase, but usually doesn’t allow you to “go back in time” to previous versions.

This property is useful in many occasions. For example, if you have a working version of some function, but you wish to make it better - add a feature, or change its internal structure (refactoring). Version control allows you to record this point in time, when you have a good, functioning function, and change the working copy of the function however you’d like. If you fail to refactor the function you can simply jump back to the latest working version.

Another important version control use case is collaborative work. Version control systems (VCS) can help you communicate changes in code base between developers, without having to somehow transfer updated versions of files from one person to the other.

There are many version control applications, but the most popular one is Git, developed by Linus Torvald (the Linux guy) in the early 2000’s. In this course we’ll be using Git with GitHub, an online backup site for your code.

There are numerous great Git and GitHub tutorials (here’s one), but for our course we’ll be using only the most basic features of Git and GitHub, so going through all features and intricacies of the software is unnecessary. General Setup and Create a Git Repository contain instructions on how to setup Git and GitHub, which should be enough for homework submission.

Git Fundamentals (Using VSCode and GitHub)#

For the purposes of this walkthrough we’ll assume we have some code inside a my_project directory, looking something like:

Git can be run from the command line, but in this quick tutorial we’ll be using the built-in Git interface in VSCode, which should be good enough for 99% of your Git needs.

To start a new Git repository and host it on GitHub:

  1. Open the project: Open VSCode, click File –> Open Folder… (or Ctrl+K Ctrl+O) and select the my_project directory. VSC My Project

  2. Initialize Git: Click the VSC Version Control icon (or Ctrl+Shift+G G), and then “Initialize Repository”.
    VSC Git init

  3. Stage: Next, we need to tell Git which files to track, which is called “staging” the files. Currently, we see our files and folders under the “Changes” headline, which means that these files are new to Git (since its previous snapshot of our repository is really a “clean slate” without any files).
    VSC VCS Changes
    There’s a U next to each file signifying that it’s “Untracked”, i.e. Git has no prior records of this file. To start tracking the files, we can click the “+” button appearing next to the U when we hover over the file. VSC Git Stage
    Alternatively, if we just want to add all files to the Git archive, we can click the “+” on the “Changes” line (again, visible when we hover with the mouse on it). Go ahead and stage all files. You should see them under a new headline: “Staged Changes”, and showing an A for “Added”. VSC Git Staged Now Git knows what’s inside the my_project folder, all files are tracked, but it hasn’t captured a snapshot of the repository status quite yet. To do that, we’ll need to “commit” the current state of the repository (repo).

  4. Commit: Committing essentially means creating a snapshot of our codebase which we can name, label, describe, and go back to. We always commit changes along with a concise message. The message goes in the line above “Staged Changes” - write “Initial commit” (it’s good enough for the purposes of the tutorial). VSC Initial Commit Now we can commit by clicking the ✓ checkmark in the line above (or Ctrl+Enter while in the commit message textbox). So far, we merely created a local repo in our computer. If the whole folder is erased, our backups and commit history go away with it as well.

  5. Push: To make an online backup of a repository, we need to first set a “remote” (i.e. a remote location containing a copy of the repository). If you don’t already have a GitHub account, create one, and then create a new repository: GitHub New Repo Give the repo a descriptive name create it. Now that you have a remote location to use (should be: https://github.com/<your-user-name>/<repo-name>), head back to VSCode and in the “Source Control” options select “Add remote”: VSC Add Remote and finally we are ready to “push” our changes: VSC Git Push

Congratulations! You’ve published your first code repository! Whether you find it trivial or confusing, you’ve made a huge step towards creating software that will not only work better for you, but also for others.

The Module System#

Namespaces#

One of the largest differences between Python and MATLAB is the concept of namespaces.

Namespaces provide a system to avoid ambiguity. We have many Luna’s, but much less Luna Kepler’s. In computers, we can have many files with the name test1.py saved in different folders of our hard drive. In contrast, we can only have one test1.py per folder, since the file system prevents these collisions.

Similarly, In Python we have to declare which namespace does our function belong to. Usually functions aren’t included in the scope of the program until we import them. After importing we can use the function with its “pathname”, i.e. the module it belongs to. This way, the same identifier (function name, class name, etc.) can be used multiple times in different modules.

You might have realised that you’re already familiar with this topic from your previous usage of functions. The following example should be clear:

a = 1
def f(a):
    """ Scopes and namespaces exemplified """
    a = 2
    print(f"Inside the function, a={a}")
f(a)
print(f"But outside of it, a={a}")
Inside the function, a=2
But outside of it, a=1

Namespaces for modules are the same. Python modules are an object with variables, classes and functions in it. A module can be a single class, a file or a folder containing files and other sub-folders. A module is brought into scope (into our namespace) with the import statement:

# The objects "math" or "pi" do not exist here yet.
import math
math.pi
3.141592653589793

pi is only defined in the context of the math module. Without the import statement there’s no special meaning attached to neither math nor pi, and they can be used as normal variables.

import os
os.sep
'/'

When we try to use a function, class or constant from a package (module) we didn’t import, we’ll receive a NameError. That’s also the exception raised when we try to use a variable that didn’t exist beforehand. This is one of the reasons we always keep our imports at the top of the file.

cos  # we probably want math.cos, but we didn't import it, so we got a NameError
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[40], line 1
----> 1 cos  # we probably want math.cos, but we didn't import it, so we got a NameError

NameError: name 'cos' is not defined

The Default Python Namespace#

Python comes with a number of built-in functions and reserved keywords:

Python Built-in Functions

Built-in functions in Python.

Python Keywords

Reserved keywords in Python.

The number of functions and reserved keywords inside the default Python namespace is extremely limited on purpose, definitely in comparison to MATLAB’s vast array of default functions - the true power of Python comes from its ecosystem. Its one of the largest and most comprehensive around, certainly for a scripting language, and allows you to do basically whatever you want with minimum effort.

The standard library of Python includes the packages that come with every installation of Python. I didn’t have to do anything special to import the math module - it was just there, “waiting” for me to import it.

Most of the functions you’d expect a programming language to have are indeed included in the standard library, available automatically to everyone who downloaded Python. Other modules, including many popular ones, are available online. We’ll discuss later how to import them.

I’ll let you discover by yourself what is included in the standard library, but some of the highlights include:

import pathlib
p = pathlib.Path(".")
print(p.absolute())
/home/runner/work/textbook-public/textbook-public/source/classes/class_1
import urllib.request
url = urllib.request.urlopen("https://www.google.com")
print(url.read())
b'<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="en"><head><meta content="Search the world\'s information, including webpages, images, videos and more. Google has many special features to help you find exactly what you\'re looking for." name="description"><meta content="noodp, " name="robots"><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"><meta content="/images/branding/googleg/1x/googleg_standard_color_128dp.png" itemprop="image"><title>Google</title><script nonce="R7aBU7YzSQV41kiEGvkyMQ">(function(){var _g={kEI:\'3cyYZuvyLbnIptQPtdKy4Ak\',kEXPI:\'0,18167,2486358,1195763,661,432,6,302123,146402,90133,2872,2891,3926,7828,31274,30022,16105,18161,145698,2,16395,342,23024,6699,41946,57737,2,2,1,10956,15676,8155,8861,14490,8701,13166,568,9779,62657,6050,30697,3801,2412,30219,3030,15816,357,1447,7734,6626,1,6651,4820,9437,11813,477,951,87,120,13492,15784,27083,5203198,9478,622,38,131,205,5991434,2839759,16,940,49,1,1,1,1,1,64,10,1,5,11,1,6,3,5,1,2,1,1,1,49,3,23937492,2772498,1271212,1008,15665,43886,3,318,4,1281,3,2124363,23029351,8163,4636,8409,8027,36664,47381,2370,6407,2956,10889,885,14280,8181,150,17726,4021,4,27529,1956,8129,8926,2661,2480,1,1,1,950,3319,155,1,1,1,2481,13504,1004,6732,6595,4,2539,740,1,3,224,3632,206,122,3217,4,3004,1116,10443,2338,1694,4082,410,518,4408,3,88,1221,7302,687,930,2817,1490,447,908,3,82,1119,3,1,416,1354,1244,2695,1543,208,2724,1643,2170,2,2487,5569,682,452,207,5676,173,1374,1981,15,2,3,383,3528,203,370,1490,436,278,7,12,623,1362,95,1110,1,6,529,4,205,62,3,1,765,69,1091,1,3,787,1601,249,294,404,4,3074,3,1061,126,1,6,534,917,66,1,731,367,3,94,290,4,724,974,3,4,307,221,66,1792,2,1261,3735,1236,946,113,1742,1,3,1545,551,138,532,253,347,2343,660,234,719,114,2341,531,438,316,91,233,299,463,17,363,648,73,51,810,134,565,98,452,105,532,1299,250,215,229,1725,267,701,572,83,547,5,221,34,366,14,606,4,114,72,276,364,67,39,1,87,5,3,3,119,1195,752,211,362,139,58,442,368,3,71,17,165,155,9,8,21,64,121,529,256,1,56,164,512,219,89,507,2,142,11,379,457,7,3,24,63,450,2,27,166,5608,2,708,302,1,139,625,1505,841,2327,21523903,5720,3,980,5,9582,2,7770,2206,20,966,1771,209,192,41,297,884,993116\',kBL:\'-Kzj\',kOPI:89978449};(function(){var a;((a=window.google)==null?0:a.stvsc)?google.kEI=_g.kEI:window.google=_g;}).call(this);})();(function(){google.sn=\'webhp\';google.kHL=\'en\';})();(function(){\nvar h=this||self;function l(){return window.google!==void 0&&window.google.kOPI!==void 0&&window.google.kOPI!==0?window.google.kOPI:null};var m,n=[];function p(a){for(var b;a&&(!a.getAttribute||!(b=a.getAttribute("eid")));)a=a.parentNode;return b||m}function q(a){for(var b=null;a&&(!a.getAttribute||!(b=a.getAttribute("leid")));)a=a.parentNode;return b}function r(a){/^http:/i.test(a)&&window.location.protocol==="https:"&&(google.ml&&google.ml(Error("a"),!1,{src:a,glmm:1}),a="");return a}\nfunction t(a,b,c,d,k){var e="";b.search("&ei=")===-1&&(e="&ei="+p(d),b.search("&lei=")===-1&&(d=q(d))&&(e+="&lei="+d));d="";var g=b.search("&cshid=")===-1&&a!=="slh",f=[];f.push(["zx",Date.now().toString()]);h._cshid&&g&&f.push(["cshid",h._cshid]);c=c();c!=null&&f.push(["opi",c.toString()]);for(c=0;c<f.length;c++){if(c===0||c>0)d+="&";d+=f[c][0]+"="+f[c][1]}return"/"+(k||"gen_204")+"?atyp=i&ct="+String(a)+"&cad="+(b+e+d)};m=google.kEI;google.getEI=p;google.getLEI=q;google.ml=function(){return null};google.log=function(a,b,c,d,k,e){e=e===void 0?l:e;c||(c=t(a,b,e,d,k));if(c=r(c)){a=new Image;var g=n.length;n[g]=a;a.onerror=a.onload=a.onabort=function(){delete n[g]};a.src=c}};google.logUrl=function(a,b){b=b===void 0?l:b;return t("",a,b)};}).call(this);(function(){google.y={};google.sy=[];var d;(d=google).x||(d.x=function(a,b){if(a)var c=a.id;else{do c=Math.random();while(google.y[c])}google.y[c]=[a,b];return!1});var e;(e=google).sx||(e.sx=function(a){google.sy.push(a)});google.lm=[];var f;(f=google).plm||(f.plm=function(a){google.lm.push.apply(google.lm,a)});google.lq=[];var g;(g=google).load||(g.load=function(a,b,c){google.lq.push([[a],b,c])});var h;(h=google).loadAll||(h.loadAll=function(a,b){google.lq.push([a,b])});google.bx=!1;var k;(k=google).lx||(k.lx=function(){});var l=[],m;(m=google).fce||(m.fce=function(a,b,c,n){l.push([a,b,c,n])});google.qce=l;}).call(this);google.f={};(function(){\ndocument.documentElement.addEventListener("submit",function(b){var a;if(a=b.target){var c=a.getAttribute("data-submitfalse");a=c==="1"||c==="q"&&!a.elements.q.value?!0:!1}else a=!1;a&&(b.preventDefault(),b.stopPropagation())},!0);document.documentElement.addEventListener("click",function(b){var a;a:{for(a=b.target;a&&a!==document.documentElement;a=a.parentElement)if(a.tagName==="A"){a=a.getAttribute("data-nohref")==="1";break a}a=!1}a&&b.preventDefault()},!0);}).call(this);</script><style>#gbar,#guser{font-size:13px;padding-top:1px !important;}#gbar{height:22px}#guser{padding-bottom:7px !important;text-align:right}.gbh,.gbd{border-top:1px solid #c9d7f1;font-size:1px}.gbh{height:0;position:absolute;top:24px;width:100%}@media all{.gb1{height:22px;margin-right:.5em;vertical-align:top}#gbar{float:left}}a.gb1,a.gb4{text-decoration:underline !important}a.gb1,a.gb4{color:#00c !important}.gbi .gb4{color:#dd8e27 !important}.gbf .gb4{color:#900 !important}\n</style><style>body,td,a,p,.h{font-family:arial,sans-serif}body{margin:0;overflow-y:scroll}#gog{padding:3px 8px 0}td{line-height:.8em}.gac_m td{line-height:17px}form{margin-bottom:20px}.h{color:#1967d2}em{font-weight:bold;font-style:normal}.lst{height:25px;width:496px}.gsfi,.lst{font:18px arial,sans-serif}.gsfs{font:17px arial,sans-serif}.ds{display:inline-box;display:inline-block;margin:3px 0 4px;margin-left:4px}input{font-family:inherit}body{background:#fff;color:#000}a{color:#681da8;text-decoration:none}a:hover,a:active{text-decoration:underline}.fl a{color:#1967d2}a:visited{color:#681da8}.sblc{padding-top:5px}.sblc a{display:block;margin:2px 0;margin-left:13px;font-size:11px}.lsbb{background:#f8f9fa;border:solid 1px;border-color:#dadce0 #70757a #70757a #dadce0;height:30px}.lsbb{display:block}#WqQANb a{display:inline-block;margin:0 12px}.lsb{background:url(/images/nav_logo229.png) 0 -261px repeat-x;color:#000;border:none;cursor:pointer;height:30px;margin:0;outline:0;font:15px arial,sans-serif;vertical-align:top}.lsb:active{background:#dadce0}.lst:focus{outline:none}</style><script nonce="R7aBU7YzSQV41kiEGvkyMQ">(function(){window.google.erd={jsr:1,bv:2043,de:true};\nvar h=this||self;var k,l=(k=h.mei)!=null?k:1,n,p=(n=h.sdo)!=null?n:!0,q=0,r,t=google.erd,v=t.jsr;google.ml=function(a,b,d,m,e){e=e===void 0?2:e;b&&(r=a&&a.message);d===void 0&&(d={});d.cad="ple_"+google.ple+".aple_"+google.aple;if(google.dl)return google.dl(a,e,d,!0),null;b=d;if(v<0){window.console&&console.error(a,b);if(v===-2)throw a;b=!1}else b=!a||!a.message||a.message==="Error loading script"||q>=l&&!m?!1:!0;if(!b)return null;q++;d=d||{};b=encodeURIComponent;var c="/gen_204?atyp=i&ei="+b(google.kEI);google.kEXPI&&(c+="&jexpid="+b(google.kEXPI));c+="&srcpg="+b(google.sn)+"&jsr="+b(t.jsr)+\n"&bver="+b(t.bv);var f=a.lineNumber;f!==void 0&&(c+="&line="+f);var g=a.fileName;g&&(g.indexOf("-extension:/")>0&&(e=3),c+="&script="+b(g),f&&g===window.location.href&&(f=document.documentElement.outerHTML.split("\\n")[f],c+="&cad="+b(f?f.substring(0,300):"No script found.")));google.ple&&google.ple===1&&(e=2);c+="&jsel="+e;for(var u in d)c+="&",c+=b(u),c+="=",c+=b(d[u]);c=c+"&emsg="+b(a.name+": "+a.message);c=c+"&jsst="+b(a.stack||"N/A");c.length>=12288&&(c=c.substr(0,12288));a=c;m||google.log(0,"",a);return a};window.onerror=function(a,b,d,m,e){r!==a&&(a=e instanceof Error?e:Error(a),d===void 0||"lineNumber"in a||(a.lineNumber=d),b===void 0||"fileName"in a||(a.fileName=b),google.ml(a,!1,void 0,!1,a.name==="SyntaxError"||a.message.substring(0,11)==="SyntaxError"||a.message.indexOf("Script error")!==-1?3:0));r=null;p&&q>=l&&(window.onerror=null)};})();</script></head><body bgcolor="#fff"><script nonce="R7aBU7YzSQV41kiEGvkyMQ">(function(){var src=\'/images/nav_logo229.png\';var iesg=false;document.body.onload = function(){window.n && window.n();if (document.images){new Image().src=src;}\nif (!iesg){document.f&&document.f.q.focus();document.gbqf&&document.gbqf.q.focus();}\n}\n})();</script><div id="mngb"><div id=gbar><nobr><b class=gb1>Search</b> <a class=gb1 href="https://www.google.com/imghp?hl=en&tab=wi">Images</a> <a class=gb1 href="https://maps.google.com/maps?hl=en&tab=wl">Maps</a> <a class=gb1 href="https://play.google.com/?hl=en&tab=w8">Play</a> <a class=gb1 href="https://www.youtube.com/?tab=w1">YouTube</a> <a class=gb1 href="https://news.google.com/?tab=wn">News</a> <a class=gb1 href="https://mail.google.com/mail/?tab=wm">Gmail</a> <a class=gb1 href="https://drive.google.com/?tab=wo">Drive</a> <a class=gb1 style="text-decoration:none" href="https://www.google.com/intl/en/about/products?tab=wh"><u>More</u> &raquo;</a></nobr></div><div id=guser width=100%><nobr><span id=gbn class=gbi></span><span id=gbf class=gbf></span><span id=gbe></span><a href="http://www.google.com/history/optout?hl=en" class=gb4>Web History</a> | <a  href="/preferences?hl=en" class=gb4>Settings</a> | <a target=_top id=gb_70 href="https://accounts.google.com/ServiceLogin?hl=en&passive=true&continue=https://www.google.com/&ec=GAZAAQ" class=gb4>Sign in</a></nobr></div><div class=gbh style=left:0></div><div class=gbh style=right:0></div></div><center><br clear="all" id="lgpd"><div id="lga"><img alt="Google" height="92" src="/images/branding/googlelogo/1x/googlelogo_white_background_color_272x92dp.png" style="padding:28px 0 14px" width="272" id="hplogo"><br><br></div><form action="/search" name="f"><table cellpadding="0" cellspacing="0"><tr valign="top"><td width="25%">&nbsp;</td><td align="center" nowrap=""><input name="ie" value="ISO-8859-1" type="hidden"><input value="en" name="hl" type="hidden"><input name="source" type="hidden" value="hp"><input name="biw" type="hidden"><input name="bih" type="hidden"><div class="ds" style="height:32px;margin:4px 0"><input class="lst" style="margin:0;padding:5px 8px 0 6px;vertical-align:top;color:#000" autocomplete="off" value="" title="Google Search" maxlength="2048" name="q" size="57"></div><br style="line-height:0"><span class="ds"><span class="lsbb"><input class="lsb" value="Google Search" name="btnG" type="submit"></span></span><span class="ds"><span class="lsbb"><input class="lsb" id="tsuid_1" value="I\'m Feeling Lucky" name="btnI" type="submit"><script nonce="R7aBU7YzSQV41kiEGvkyMQ">(function(){var id=\'tsuid_1\';document.getElementById(id).onclick = function(){if (this.form.q.value){this.checked = 1;if (this.form.iflsig)this.form.iflsig.disabled = false;}\nelse top.location=\'/doodles/\';};})();</script><input value="AL9hbdgAAAAAZpja7WbB50HxqJAFbSrsKnWwqcqaK3Ga" name="iflsig" type="hidden"></span></span></td><td class="fl sblc" align="left" nowrap="" width="25%"><a href="/advanced_search?hl=en&amp;authuser=0">Advanced search</a></td></tr></table><input id="gbv" name="gbv" type="hidden" value="1"><script nonce="R7aBU7YzSQV41kiEGvkyMQ">(function(){var a,b="1";if(document&&document.getElementById)if(typeof XMLHttpRequest!="undefined")b="2";else if(typeof ActiveXObject!="undefined"){var c,d,e=["MSXML2.XMLHTTP.6.0","MSXML2.XMLHTTP.3.0","MSXML2.XMLHTTP","Microsoft.XMLHTTP"];for(c=0;d=e[c++];)try{new ActiveXObject(d),b="2"}catch(h){}}a=b;if(a=="2"&&location.search.indexOf("&gbv=2")==-1){var f=google.gbvu,g=document.getElementById("gbv");g&&(g.value=a);f&&window.setTimeout(function(){location.href=f},0)};}).call(this);</script></form><div id="gac_scont"></div><div style="font-size:83%;min-height:3.5em"><br></div><span id="footer"><div style="font-size:10pt"><div style="margin:19px auto;text-align:center" id="WqQANb"><a href="/intl/en/ads/">Advertising</a><a href="/services/">Business Solutions</a><a href="/intl/en/about.html">About Google</a></div></div><p style="font-size:8pt;color:#70757a">&copy; 2024 - <a href="/intl/en/policies/privacy/">Privacy</a> - <a href="/intl/en/policies/terms/">Terms</a></p></span></center><script nonce="R7aBU7YzSQV41kiEGvkyMQ">(function(){window.google.cdo={height:757,width:1440};(function(){var a=window.innerWidth,b=window.innerHeight;if(!a||!b){var c=window.document,d=c.compatMode=="CSS1Compat"?c.documentElement:c.body;a=d.clientWidth;b=d.clientHeight}\nif(a&&b&&(a!=google.cdo.width||b!=google.cdo.height)){var e=google,f=e.log,g="/client_204?&atyp=i&biw="+a+"&bih="+b+"&ei="+google.kEI,h="",k=[],l=window.google!==void 0&&window.google.kOPI!==void 0&&window.google.kOPI!==0?window.google.kOPI:null;l!=null&&k.push(["opi",l.toString()]);for(var m=0;m<k.length;m++){if(m===0||m>0)h+="&";h+=k[m][0]+"="+k[m][1]}f.call(e,"","",g+h)};}).call(this);})();</script>  <script nonce="R7aBU7YzSQV41kiEGvkyMQ">(function(){google.xjs={basecomb:\'/xjs/_/js/k\\x3dxjs.hp.en.aXD505qPp04.O/ck\\x3dxjs.hp.oyiQ3IvZjL4.L.X.O/am\\x3dAQAAEAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAwAA4AAAAEAAgEAAAAAMADAAAAAAAAAAAgAMAABATAgfiOAEAALAIAwAs/d\\x3d1/ed\\x3d1/dg\\x3d0/ujg\\x3d1/rs\\x3dACT90oGCn7QnL4pHoZ_4-E8DNubVymAxYA\',basecss:\'/xjs/_/ss/k\\x3dxjs.hp.oyiQ3IvZjL4.L.X.O/am\\x3dAQAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAgEAAAAAAAAAAAAAAAAAAAgAMAABAQ/rs\\x3dACT90oGUD5B4dLZnp4L7IqrsNt_rSq7OVw\',basejs:\'/xjs/_/js/k\\x3dxjs.hp.en.aXD505qPp04.O/am\\x3dAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAwAA4AAAAAAAAAAAAAAMADAAAAAAAAAAAAAAAAAADAgfiOAEAALAIAwAs/dg\\x3d0/rs\\x3dACT90oGyfIuoj6fZzVhohQphyaGOD38lHA\',excm:[]};})();</script>  <link href="/xjs/_/ss/k=xjs.hp.oyiQ3IvZjL4.L.X.O/am=AQAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAgEAAAAAAAAAAAAAAAAAAAgAMAABAQ/d=1/ed=1/rs=ACT90oGUD5B4dLZnp4L7IqrsNt_rSq7OVw/m=sb_he,d" rel="stylesheet" nonce="R7aBU7YzSQV41kiEGvkyMQ">      <script nonce="R7aBU7YzSQV41kiEGvkyMQ">(function(){var u=\'/xjs/_/js/k\\x3dxjs.hp.en.aXD505qPp04.O/am\\x3dAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAwAA4AAAAAAAAAAAAAAMADAAAAAAAAAAAAAAAAAADAgfiOAEAALAIAwAs/d\\x3d1/ed\\x3d1/dg\\x3d3/rs\\x3dACT90oGyfIuoj6fZzVhohQphyaGOD38lHA/m\\x3dsb_he,d\';var st=1;var amd=1000;var mmd=0;var pod=true;\nvar f=this||self,g=function(a){return a};var h;var k=function(a){this.g=a};k.prototype.toString=function(){return this.g+""};var l=function(a){return a instanceof k&&a.constructor===k?a.g:"type_error:TrustedResourceUrl"},m={};\nvar n=/^\\s*(?!javascript:)(?:[\\w+.-]+:|[^:/?#]*(?:[/?#]|$))/i;var p="alternate author bookmark canonical cite help icon license modulepreload next prefetch dns-prefetch prerender preconnect preload prev search subresource".split(" ");function q(a,b){a.src=l(b);var c,d;(c=(b=(d=(c=(a.ownerDocument&&a.ownerDocument.defaultView||window).document).querySelector)==null?void 0:d.call(c,"script[nonce]"))?b.nonce||b.getAttribute("nonce")||"":"")&&a.setAttribute("nonce",c)};var r=function(a){var b=document;a=String(a);b.contentType==="application/xhtml+xml"&&(a=a.toLowerCase());return b.createElement(a)};function t(a){a=a===null?"null":a===void 0?"undefined":a;if(h===void 0){var b=null;var c=f.trustedTypes;if(c&&c.createPolicy){try{b=c.createPolicy("goog#html",{createHTML:g,createScript:g,createScriptURL:g})}catch(d){f.console&&f.console.error(d.message)}h=b}else h=b}a=(b=h)?b.createScriptURL(a):a;return new k(a,m)};google.ps===void 0&&(google.ps=[]);function w(){var a=u,b=function(){};google.lx=google.stvsc?b:function(){x(a);google.lx=b};google.bx||google.lx()}function y(a,b){b&&q(a,t(b));var c=a.onload;a.onload=function(d){c&&c(d);google.ps=google.ps.filter(function(e){return a!==e})};google.ps.push(a);document.body.appendChild(a)}google.as=y;function x(a){google.timers&&google.timers.load&&google.tick&&google.tick("load","xjsls");var b=r("SCRIPT");b.onerror=function(){google.ple=1};b.onload=function(){google.ple=0};google.xjsus=void 0;y(b,a);google.aple=-1;google.dp=!0}\nfunction z(){var a=[u];if(!google.dp){for(var b=0;b<a.length;b++){var c=r("LINK"),d=c,e=t(a[b]);if(e instanceof k)d.href=l(e).toString(),d.rel="preload";else{if(p.indexOf("preload")===-1)throw Error("a`preload");e=n.test(e)?e:void 0;e!==void 0&&(d.href=e,d.rel="preload")}c.setAttribute("as","script");document.body.appendChild(c)}google.dp=!0}};function A(a){var b=a.getAttribute("jscontroller");return(b==="UBXHI"||b==="R3fhkb"||b==="TSZEqd")&&a.hasAttribute("data-src")}function B(){for(var a=document.getElementsByTagName("img"),b=0,c=a.length;b<c;b++){var d=a[b];if(d.hasAttribute("data-lzy_")&&Number(d.getAttribute("data-atf"))&1&&!A(d))return!0}return!1}for(var C=document.getElementsByTagName("img"),D=0,E=C.length;D<E;++D){var F=C[D];Number(F.getAttribute("data-atf"))&1&&A(F)&&(F.src=F.getAttribute("data-src"))};var G,H,I,J,K;function L(){google.xjsu=u;f._F_jsUrl=u;J=function(){w()};G=!1;H=(st===1||st===3)&&!!google.caft&&!B();I=(st===2||st===3)&&!!google.rairicb&&!B();K=pod}function M(){G||H||I||(J(),G=!0)}setTimeout(function(){google&&google.tick&&google.timers&&google.timers.load&&google.tick("load","xjspls");L();if(H||I){if(H){var a=function(){H=!1;M()};google.caft(a);window.setTimeout(a,amd)}I&&(a=function(){I=!1;M()},(0,google.rairicb)(a),window.setTimeout(a,mmd));K&&(G||z())}else J()},0);})();window._ = window._ || {};window._DumpException = _._DumpException = function(e){throw e;};window._s = window._s || {};_s._DumpException = _._DumpException;window._qs = window._qs || {};_qs._DumpException = _._DumpException;(function(){var t=[268435457,0,0,0,0,10485760,0,209879040,268963331,536875008,134217792,15728712,0,0,201327104,805372168,9369729,145785088,12320768];window._F_toggles = window._xjs_toggles = t;})();window._F_installCss = window._F_installCss || function(css){};(function(){google.jl={bfl:0,dw:false,ine:false,ubm:false,uwp:true,vs:false};})();(function(){var pmc=\'{\\x22d\\x22:{},\\x22sb_he\\x22:{\\x22agen\\x22:false,\\x22cgen\\x22:false,\\x22client\\x22:\\x22heirloom-hp\\x22,\\x22dh\\x22:true,\\x22ds\\x22:\\x22\\x22,\\x22fl\\x22:true,\\x22host\\x22:\\x22google.com\\x22,\\x22jsonp\\x22:true,\\x22msgs\\x22:{\\x22cibl\\x22:\\x22Clear Search\\x22,\\x22dym\\x22:\\x22Did you mean:\\x22,\\x22lcky\\x22:\\x22I\\\\u0026#39;m Feeling Lucky\\x22,\\x22lml\\x22:\\x22Learn more\\x22,\\x22psrc\\x22:\\x22This search was removed from your \\\\u003Ca href\\x3d\\\\\\x22/history\\\\\\x22\\\\u003EWeb History\\\\u003C/a\\\\u003E\\x22,\\x22psrl\\x22:\\x22Remove\\x22,\\x22sbit\\x22:\\x22Search by image\\x22,\\x22srch\\x22:\\x22Google Search\\x22},\\x22ovr\\x22:{},\\x22pq\\x22:\\x22\\x22,\\x22rfs\\x22:[],\\x22stok\\x22:\\x228MVX7VGCQGf9HRLYXHwnTBNQMlQ\\x22}}\';google.pmc=JSON.parse(pmc);})();(function(){var b=function(a){var c=0;return function(){return c<a.length?{done:!1,value:a[c++]}:{done:!0}}};\nvar e=this||self;var g,h;a:{for(var k=["CLOSURE_FLAGS"],l=e,n=0;n<k.length;n++)if(l=l[k[n]],l==null){h=null;break a}h=l}var p=h&&h[610401301];g=p!=null?p:!1;var q,r=e.navigator;q=r?r.userAgentData||null:null;function t(a){return g?q?q.brands.some(function(c){return(c=c.brand)&&c.indexOf(a)!=-1}):!1:!1}function u(a){var c;a:{if(c=e.navigator)if(c=c.userAgent)break a;c=""}return c.indexOf(a)!=-1};function v(){return g?!!q&&q.brands.length>0:!1}function w(){return u("Safari")&&!(x()||(v()?0:u("Coast"))||(v()?0:u("Opera"))||(v()?0:u("Edge"))||(v()?t("Microsoft Edge"):u("Edg/"))||(v()?t("Opera"):u("OPR"))||u("Firefox")||u("FxiOS")||u("Silk")||u("Android"))}function x(){return v()?t("Chromium"):(u("Chrome")||u("CriOS"))&&!(v()?0:u("Edge"))||u("Silk")}function y(){return u("Android")&&!(x()||u("Firefox")||u("FxiOS")||(v()?0:u("Opera"))||u("Silk"))};var z=v()?!1:u("Trident")||u("MSIE");y();x();w();var A=!z&&!w(),D=function(a){if(/-[a-z]/.test("ved"))return null;if(A&&a.dataset){if(y()&&!("ved"in a.dataset))return null;a=a.dataset.ved;return a===void 0?null:a}return a.getAttribute("data-"+"ved".replace(/([A-Z])/g,"-$1").toLowerCase())};var E=[],F=null;function G(a){a=a.target;var c=performance.now(),f=[],H=f.concat,d=E;if(!(d instanceof Array)){var m=typeof Symbol!="undefined"&&Symbol.iterator&&d[Symbol.iterator];if(m)d=m.call(d);else if(typeof d.length=="number")d={next:b(d)};else throw Error("b`"+String(d));for(var B=[];!(m=d.next()).done;)B.push(m.value);d=B}E=H.call(f,d,[c]);if(a&&a instanceof HTMLElement)if(a===F){if(c=E.length>=4)c=(E[E.length-1]-E[E.length-4])/1E3<5;if(c){c=google.getEI(a);a.hasAttribute("data-ved")?f=a?D(a)||"":"":f=(f=\na.closest("[data-ved]"))?D(f)||"":"";f=f||"";if(a.hasAttribute("jsname"))a=a.getAttribute("jsname");else{var C;a=(C=a.closest("[jsname]"))==null?void 0:C.getAttribute("jsname")}google.log("rcm","&ei="+c+"&tgtved="+f+"&jsname="+(a||""))}}else F=a,E=[c]}window.document.addEventListener("DOMContentLoaded",function(){document.body.addEventListener("click",G)});}).call(this);</script></body></html>'

It can be quite irritating to write the full namespace address of all functions. To this end we have the from and as keywords:

from math import cos as cosine
cosine(1.)
0.5403023058681398

Enumerations are great, use this opportunity to familiarize youself with them:

from enum import Enum
from pprint import pprint

class Day(Enum):
    SUNDAY = 1
    MONDAY = 2
    TUESDAY = 3
    WEDNESDAY = 4
    THURSDAY = 5
    FRIDAY = 6
    SATURDAY = 7

d = Day.MONDAY

print(d)
pprint(d)
Day.MONDAY
<Day.MONDAY: 2>
from random import randint, randrange
# Instead (or on top) of from:
import multiprocessing.pool as mpool
# A non-recommended version of importing is the star import
from math import *
print(pi)
print(e)
print(cos(pi))
3.141592653589793
2.718281828459045
-1.0

The list of all standard library modules in Python 3 is here. However, it’s just a drop in the ocean that is the Python ecosystem.

pip and External Modules#

As noted before, Python’s gigantic ecosystem is one of its biggest strengths. And the fact that you can easily install many of these package with a simple command-line instruction is even more important. The standard tool to do that is pip, a recursive acronym that stands for “pip installs Python”.

pip itself is a Python program, but its not run from inside the Python interpreter. Instead its run from a shell - the Windows command line (or PowerShell), for example, or the Mac’s terminal - making it an external application to Python. Happily enough, basically all Python distributions come with pip pre-installed, so you don’t have to install it yourself.

To install a library with pip, open a command line (in VSCode you can simply press Ctrl + ~) and type pip install package_name.

pip, like any package manager, has two main jobs. The first is to provide a convenient API to a package repository. In essence, it’s a download manager for a single site - the Python Package Index (PyPI, pronounced pai-pee-eye), the official Python repository for packages.

PyPI holds installation files for the packages hosted in it, alongside with some metadata, like version number and dependencies. pip (and other tools we’ll discuss soon) downloads the wanted package from PyPI, together with its dependencies, and installs them in a pre-defined location in our personal computer.

pip’s second important job is handling dependencies. Many packages rely on other packages, which in turn rely on other, more basic packages, finally leading to the basic Python interpreter. pip has to make sure to install all dependencies of the package you’re currently after, and to avoid any collisions with other installed packages. For example, a common problem in the Python world is the Python 2-3 schism, which means that packages written for Python 2 can’t run on a Python 3 interpreter, and vice versa. The package manger’s job is to grant you the right version of the package you’re looking for.

Comparison with MATLAB’s approach#

We should take a minute here to contrast Python’s approach with that of MATLAB. In MATLAB, once we added a directory to the path using pathtool, each file in that file is now directly in the MATLAB namespace. This means that we don’t have to import anything - adding something to pathtool is essentially importing the entire folder into the general namespace, which is the only namespace in MATLAB world. This is a pretty straight-forward approach, but it’s also one that no other programming language, especially a modern one, uses. This is because cluttering the one and only namespace is a bad idea, since you can quite easily overwrite names of functions from files with names of variables you use, and you won’t even know it. Moreover, you don’t need all functions around all the time. Each file and project will usually need a few different functions from a couple of toolboxes, and that’s it.

In Python we have another layer between our code and the import-able functions. Inside our code we can only use built-in functions (list(), int(), print(), etc.) and functions that we explicitly imported, which will vary between files and project. In addition, we can only call import on files and packages which are in our Python’s path. We’ll discuss Python’s equivilant of pathtool in the next class, but for now you should see the separation Python forces on us between the specific file’s namespace and the general Python namespace (what packages can we even import).

File Structure#

import statements are useful for more than just importing code - they’re also our way of arranging our project’s files. Here’s the standard arrangement of files I’d like you to use throughout the course:

The base folder can contain many other files, including sample data, for example. The point here is that your actual code in confined to the project_name folder, which has an empty __init__.py. This file allows Python to import user-defined objects from that folder.

Thus, if you wish to use a function you defined in class_1.py in class_2.py, you should write inside class_2.py the following statement: from class_1 import my_func.

Scripts and Functions#

If you’re familiar with MATLAB there’s a good chance that you’ve written a script before. A script is a file which is run sequentially, while using other functions and definitions. In the Python world people often prefer to stay away from scripts.

If you wish to run a module from the command line, you should follow this general pattern:

def run_main():
    # Program logic...
    return result

if __name__ == '__main__':
    run_main()

Every Python file which is being run has a caller. If this file was run directly from the Python interpreter its __name__ will be '__main__'. This if statement basically tells the Python interpreter “Start from here”, and is the conventional way to run Python procedures.

In this course you’re highly encouraged to divide your code into many small functions and methods in well-defined compact classes. Each method should have a single purpose, documented in its docstring. Each class should have a logical structure that envelopes its methods and attributes in a sensible way.

Beware of God classes, or God scripts and functions. These are monolithic objects that encompass the entirety of your application, and are very hard to reason about. Simply create more files, each with a descriptive name and a bunch of related functions, and import this file into the other folders and main file.

Another important reason to partition our code into many small bits is unit testing which we’ll cover later on in the course.

Importing code from one file to the other isn’t as easy as you’d like it to be, especially since each language deals with this issue in a different way. It’s completely expected that you won’t be able to ‘nail’ the first couple of import statements you write. Keep trying, verify that your code is in the right directory structure, and don’t be afraid to ask friends, Google or me.

Version Control and Modules Exercise

Initialize a new git archive containing the two modules you created in this lesson’s exercises and upload it to a new repository on GitHub.