<jupyter_start><jupyter_text>Super-minimal Python What is "Python"When people talk about "Python" they usually mean four things. (1) The python language itself, (2) the python software that we use e.g. to execute a script ```python myscript.py```, (3) the python functions and modules that come automatically with Python, (4) the external packages that we can install to achieve specific tasks (e.g working with matrices, images etc.)We are going to now briefly review the essential python *language* needed for this course. This includes variables, some containers, flow control, functions and modules. Most of the more complex operations we are going to use come from external packages that we are going to explore in detail later. **Be aware that this is not an exhaustive Python description, but really the bare minimum needed for this course**. If you decide to seriously use Python for image processing, it's probably good to once read more in detail about Python basics. We can recommend for example <NAME>' book [A Whirlwind Tour of Python](https://jakevdp.github.io/WhirlwindTourOfPython/) entirely available as interactive notebooks.  VariablesLike any other programming language, Python works with variables: as we do in algebra, instead of directly working with numbers we work with symbols that can take different values. So we can just write:<jupyter_code>a = 3<jupyter_output><empty_output><jupyter_text>And then re-use the variable ```a``` in our program, or here in our notebook. For example:<jupyter_code>3*a<jupyter_output><empty_output><jupyter_text>Dynamic typingUnlike e.g. C++ where you have to say in advance what type your variable has (integer, float, string etc.) Python does that for you by *interpreting* your intensions. This is useful and makes writing code much simpler, **but remember that this can be the source of bugs.** Let's create a few variables:<jupyter_code>a = 3
b = 1.2
c = '<PASSWORD>'
d = True<jupyter_output><empty_output><jupyter_text>We can use the ```type``` function to get the variable type:<jupyter_code>type(a)
type(b)
type(c)
type(d)<jupyter_output><empty_output><jupyter_text>Note that if we start mixing variables, Python is also going to interpret the resulting type for us:<jupyter_code>e = a * b
type(e)<jupyter_output><empty_output><jupyter_text>Simple operationsAs already seen above you can do simple mathematics with variables as signs such as ```+``` and ```*``` are understood by Python.In addition to those, you can also perform logical operations with boolean variables. A boolean can be ```True``` or ```False``` and is typically generated via a comparison:<jupyter_code>a > b
my_boolean = a > b
my_boolean
my_boolean2 = e == a
my_boolean2
my_boolean and my_boolean2
my_boolean or my_boolean2<jupyter_output><empty_output><jupyter_text>We will see that booleans are helpful when dealing with image masks.  FunctionsPython functions are just like mathematical functions $y = f(x)$, they have a name ```f```, take an input ```x``` and give an output ```y```. We have just seen an example of such a function above with ```type()```. Function definitions can come from different places: Built-in functionsThese functions are directly implemented in Python and you don't have to do anything special to use them. You can find a list [here](https://docs.python.org/3/library/functions.html). For example, you can take an absolute value:<jupyter_code>a = -3
b = abs(a)
b<jupyter_output><empty_output><jupyter_text>Module functionsAll additional functions come from modules. Those are separate software packages logically organised by domains (handling files, doing maths etc.) that one can import in a program. There are two types of modules: some are native to Python (no installation necessary) and some are distributed externally (and need to be installed using e.g. pip or conda).Let's imagine we want to know the content of folders e.g. to execute some program on all images within a folder. We can use for that the ```os``` package. We import it using:<jupyter_code>import os<jupyter_output><empty_output><jupyter_text>Then all functions of the ```os``` module are available using the dot notations:<jupyter_code>mypath = os.getcwd()
mypath<jupyter_output><empty_output><jupyter_text>The ```getcwd()``` function gives us the current path. In this particular case, there is no input, just an output in the form of a path string.Some functions that logically belong together are also sometimes grouped together into sub-modules, that one can call again using the "dot noation". For example we can do all sorts of operations on path strings using the functions grouped in the ```os.path``` sub-module:<jupyter_code>os.path.basename(mypath)<jupyter_output><empty_output><jupyter_text>The function above takes a path as input and returns the last part of the path.External packages that we installed ourselves are imported in the exact same way. For example we are going to use a lot Numpy, which allows us to do matrix computations:<jupyter_code>import numpy<jupyter_output><empty_output><jupyter_text>Note that to abbreviate code, it is very common to abbreviate the name of imported packages. This can be done like this:<jupyter_code>import numpy as np<jupyter_output><empty_output><jupyter_text>Note that in some cases, sub-modules need to be imported separately. For example the import/export module of the image processing package scikit-image:<jupyter_code>import skimage.io<jupyter_output><empty_output><jupyter_text>Your own functionIf is very easy to create a function in Python. For example the minimum you need to define a parabola function ($f(x) = x^2$) is:<jupyter_code>def parabola(x):
    y = x**2<jupyter_output><empty_output><jupyter_text>```def``` indicates that we define a parabola. The ```parabola``` *name* is chosen by us. Like in algebra we  have a parenthesis with *variables*, here ```x```. Then we have a column ```:``` and everything that follows it and which is **indented** belongs to the *function body*. We can then use the function *name* and pick a parameter value of our choice:<jupyter_code>result = parabola(3)
result is None<jupyter_output><empty_output><jupyter_text>We see that the last problem we have is that the function doesn't return any value. It does the computation but keeps it hidden from us. The solution to this is to explicitly say what we want to ```return```:<jupyter_code>def parabola(x):
    y = x**2
    return y
result = parabola(3)
result<jupyter_output><empty_output><jupyter_text>We can naturally have more that one parameter to pass to the function (e.g. $f(x,a, b) = a * x^2+b$). Parameters can also have a default value as shown here, where the parameter ```b == 2```:<jupyter_code>def parabola2(x, a, b=2):
    y = a * x**2 + b
    return y
parabola2(3,0)<jupyter_output><empty_output><jupyter_text>The default can of course be overriden by setting an explicit value:<jupyter_code>parabola2(3,0,10)<jupyter_output><empty_output><jupyter_text>ContainersBeyond simple variables, Python offers several types of containers that can use multiple variables.  Lists The simplest one is the list, enclosed by ```[]```:<jupyter_code>mylist = [1,6,10,20]<jupyter_output><empty_output><jupyter_text>A list can contain any type of variable, and you can even mix them, even though that happends rarely:<jupyter_code>mylist_mix = [1, 6.8, 'this is my text',[9,10,3]]
mylist_mix<jupyter_output><empty_output><jupyter_text>DictionariesDictionaries are pairs sets of pairs of definitions and values, like an actual dictionary. They are creted in the following way:<jupyter_code>mydict = {'name': 'apple', 'weight': 100, 'diameter': 10}
mydict<jupyter_output><empty_output><jupyter_text>Dictionaries are useful to reference properties of objects e.g. the geometric properties of segmented objects in an image.  Accessing/modifying list elementsElements of both lists and dictionaries can be accessed using square bracktets ```[]```. List elements are selectable via a *positional index* (starting at 0), e.g. if you want to access the third element you do:<jupyter_code>mylist_mix[2]<jupyter_output><empty_output><jupyter_text>And dictionary elements can be accessed via the definitions or keys:<jupyter_code>mydict['weight']<jupyter_output><empty_output><jupyter_text>You can also modify an element in the same way:<jupyter_code>mylist_mix[2] = 'new text'
mylist_mix
mydict['weight'] = 200
mydict<jupyter_output><empty_output><jupyter_text>Copies**Beware that simple copies of lists and dictionaries are not independent copies!**:<jupyter_code>mylist_mix2 = mylist_mix
mylist_mix[0] = 'I changed this'
mylist_mix
mylist_mix2<jupyter_output><empty_output><jupyter_text>To create a **true independent copy** you can use the ```copy()``` methods on your list:<jupyter_code>mylist_mix2 = mylist_mix.copy()
mylist_mix[0] = 'again'
mylist_mix
mylist_mix2<jupyter_output><empty_output><jupyter_text>Python variables are objectsIf you are familiar with programming you probably know about object oriented programming. If not, just know that in programming an object can have "features" (attributes) and "functionalities" (methods). Since every Python variable is an object, it means that each variable, on top of its *value*, can contain additional information. Let's look at a practical example. We define an complex number:<jupyter_code>imaginary = 3 + 5j
type(imaginary)<jupyter_output><empty_output><jupyter_text>Even this simple variable has both *attributes* and *methods*. For example we can recover the real and imaginary parts of the number through the attributes ```real``` and ```img```. This is done with the *dot-notation*:<jupyter_code>imaginary.real
imaginary.imag<jupyter_output><empty_output><jupyter_text>*Methods* are accessed with the same *dot-notation*. However since they are actual functions, we need to use parentheses. For example, we can create a string variable and then use it's ```count()``` method to count the number of occurrences of a given letter:<jupyter_code>mystring = 'this is my string'
mycounts = mystring.count('i')
print(mystring)
mycounts<jupyter_output>this is my string<jupyter_text>Flow controlLike any other programming language, Python posesses flow control mechanisms to repeat operations (for, while) or separate different cases (if, else). The main specificities of Python are:- **INDENTATION:** All the commands that belong to the same flow contol block have to be indentend at the same level. Let's create a simle for loop- flow controls start after ```:```- no parenthesis or special signs are used in the statement which reads almost like english<jupyter_code>mylist = [1,2,3,4,5,6,7,8]
for x in mylist:
    y = x**2
    print(y)<jupyter_output>1
4
9
16
25
36
49
64<jupyter_text>If we un-indent the print statement it doesn't belong to the loop anymore and is executed *after* it:<jupyter_code>for x in mylist:
    y = x**2
print(y)<jupyter_output>64<jupyter_text>If statements follow the same rules:<jupyter_code>a = 3
b = 5

if a > b:
    print('yes')
else:
    print('no')<jupyter_output>no<jupyter_text>Variable scopeThe last point which brings sometimes confusion is the in what context variables are available or in other words their *scope*. Let's imagine the following code:<jupyter_code>a = 1

def myfun():
    a = 2
    print(f'value in myfun: {a}')
    
def myfun2():
    print(f'value in myfun2: {a}')
    
print(f'initial defintion: {a}')

myfun()
myfun2()
print(f'final defintion: {a}')<jupyter_output>initial defintion: 1
value in myfun: 2
value in myfun2: 1
final defintion: 1<jupyter_text>So first we define ```a = 1```. Then we define two functions. In one of them we redefine the same variable as ```a = 2``` and print it, in the second one, we use it without redefining it. From the print statements we see that:1. The variable ```a``` defined in the notebook (or script) is **unaffected** by what happens in functions.2. If the same variable is redefined within a function it is a **new and independent** variable.3. A variable defined in the notebook (or script) is **globally** available to the functions used there.Finally, let's see what happens at the level of modules. In the course_functions.py module, we defined the same functions as ```myfun``` and ```myfun2```. We load them first and then call them:<jupyter_code>from course_functions import myfun_in_script, myfun_in_script2
myfun_in_script()
myfun_in_script2()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08756834995175297, 'fraction_numerical': 0.008845287873914441, 'mean_word_length': 5.848568281938326, 'pattern_counts': {'":': 0, '<': 134, '<?xml version=': 0, '>': 137, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '34', 'dir': 'jupyter-structured-clean-dedup', 'id': '332445', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 3732, 'n_tokens_neox': 3643, 'n_words': 1566, 'path': '01-Python_bare_minimum.ipynb', 'repo_name': 'vmezzano21/neubias_academy_biapy', 'size': '12442'}