# My Python programming notes

I only use Python to analyze some data output by my CUDA codes. So I will not bother to learn everything about Python. For me, Python is just a free Matlab. Here are my notes taken along with my reading of the official Python tutorials:

# Python basics

## Numerical data types and arithmetics

• No need to define a type explicitly. This is similar to Matlab and is opposite to C/C++.

• Division (/) always returns a float. To do floor division and get an integer result, use the // operator.
```
>>> 17 / 3
5.666666666666667
>>> 17 // 3
5

```

• Complex number (similar to Matlab)
```3 + 5j
```

## Sequence types

### Strings

• Strings are immutable.

### Lists

• Definition: a list of comma-separated values (items) between square brackets
• List is mutable:
```>>> cubes = [1, 8, 27, 65, 125]
>>> cubes = 64
>>> cubes
[1, 8, 27, 64, 125]
```

• List can be nested:
```>>> a = ['a', 'b', 'c']
>>> n = [1, 2, 3]
>>> x = [a, n]
>>> x
[['a', 'b', 'c'], [1, 2, 3]]
>>> x
['a', 'b', 'c']
>>> x
'b'
```

### Tuples

• Definition: a collection of comma-separated values (optionally) between parentheses
• Tuple is immutable

## Control flow

### `if`

• This is no `switch-case`

### `for` loops

• C-style:
```words = ['I', 'am', 'Bruce']
for i in range(len(words)):
print(words[i], len(words[i]))
```
• C++ style:
```words = ['I', 'am', 'Bruce']
for w in words:
print(w, len(w))
```
• There are `break` and `continue` as in C/C++.
• There is also `else` for `for`, but it looks weird. I would not use it.

## Functions

• How to define a simple function:
```def my_function(x):

"""
return x * 2
print(my_function(2)) # call a function
```
• Arguments are passed by value:
```def my_function(x):

"""
x = x * 2
return x
x = 2
print(x)
my_function(x)
print(x)
```

The above script gives:

```2
2
```

```2
4
```
• Ugly things that I would not use:
• Default arguments
• Keyword arguments
• Arbitrary argument lists
• Unpacking argument lists
• Lambda expressions

## Modules and Packages

### Modules

• How to define a module? Just put something in a file with a name something like `my_module.py`. For example, create a file `my_module.py` with the following contents:
```def my_function(x):

"""
x = x * 2
return x
```
• Import a module and use it:
```import my_module
print(my_module.my_function(1))
```
• Import a module and give it a shorter name:
```import my_module as mm
print(mm.my_function(1.6))
```
• Import something form a module:
```from my_module import my_function
print(my_function(1.6))
```
• Import something form a module and give it a shorter name:
```from my_module import my_function as mf
print(mf(1.6))
```

• The module name is kept in the variable `__name__`. When you run a Python module from the command line:
```python my_module.py
```

`__name__` will be set to `"__main__"`. We can use the feature to add some testing code at the end of a module:

```if __name__ == "__main__":
print(my_function(1.6))
```

This part of code will not be executed when the module is imported.

### Packages

• A package is collection of modules, which are put into a directory. The `__init__.py` file is required to make Python treat a directory containing this file as a package. In the simplest case, `__init__.py` can just be an empty file.
• An example of creating a package:
• Step 1: create a directory named `my_package` and create an empty file named `__init__.py` within it.
• Put our previous module file `my_module.py` into the directory. You can put more module files into the directory. It's done!
• Examples of using the package we created:
• Using the full name:
```import my_package.my_module
print(my_package.my_module.my_function(1.6))
```
• Using the module name directly:
```from my_package import my_module
print(my_module.my_function(1.6))
```
• Using the function name directly:
```from my_package.my_module import my_function
print(my_function(1.6))
```

## Inputs and Outputs

### The String format() Method

• Here is an example:
```from my_package.my_module import my_function
print('my_function({}) = {}.'.format(1.6, str(my_function(1.6))))
```

The output is:

```my_function(1.6) = 3.2.
```

### Using files

#### Open and close a file

Example

```my_file = open('my_file.txt', 'r') # The file should exist
print(my_file.closed) # the output is False
my_file.close()
print(my_file.closed) # the output is True
```

Here are the modes:

• r: read (this is the default)
• w: write
• a: append
• A recommended way to open a file which will be automatically closed:
```with open('my_file.txt', 'r') as my_file:
print(my_file.closed) # the output is True
```

#### Writing to file

Example:

```from my_package.my_module import my_function
my_string = 'my_function({}) = {}.'.format(1.6, str(my_function(1.6)))
with open('my_file.txt', 'w') as my_file:
my_file.write(my_string)
```

```with open('my_file.txt', 'r') as my_file:
print(my_string)  # gives 'my_function(1.6) = 3.2.'
```

• Read some of the file:
```with open('my_file.txt', 'r') as my_file:
print(my_string) # gives 'my_function'
```

```with open('my_file.txt', 'r') as my_file:
print(my_string) # gives 'my_function(1.6) = 3.2.'
```

# `Numpy`

## What is NumPy?

• NumPy is the fundamental package for scientific computing in Python.
• At the core of the NumPy package, is the ndarray object.
• To use NumPy, one needs to import it. The standard way is as follows:
```import numpy as np
```
• In NumPy, there are two classes, `np.array` and `np.matrix`. The latter will be deprecated soon. So do not use it.

## Array Creation

### Using `array` from list

• Examples:
```a = np.array([1,2,3,4]) # correct
a = np.array(1,2,3,4)   # wrong!
a = np.array([[1,2],[3,4]]) # Two-dimensional array
```
• Specify data type:
```a = np.array([[1,2], [3,4]])
print(a.dtype) # dtype('int64')
b = np.array([[1,2], [3,4]], dtype=complex)
print(b.dtype) # dtype('complex128')
```

### Using `arange`

```a = np.arange(4) # gives array([0, 1, 2, 3])
a = np.arange(0, 10, 2) # gives array([0, 2, 4, 6, 8])
```

### Using `linspace`

```a = np.linspace(0, 2 * np.pi, 100)
```

### Special arrays

• All zeros or all ones:
```np.zeros((3, 4)) # 3*4 matrix with all elements being 0.0
np.ones(3)       # 1*3 matrix with all elements being 1.0
np.zeros((3, 4), dtype = np.int32) # 3*4 matrix with all elements being 0
```
• Identity matrix:
```a = np.eye(3) # eye means I
a = np.identity(3) # almost the same as above
```
• Random matrix
```a = np.random.random((3,4)) # argument is a tuple, very ugly!
a = np.random.rand(3,4) # almost equivalent to the above
a = np.random.randn(10, 10) # normal distribution
```

• Suppose there is a text file with name `x.txt` with contents:
```1 2 3 4
5 6 7 8
```

Then we can load the data in the file into a Numpy array as follows:

```x = np.loadtxt('x.txt')
print(x) # check it
```

The output is

```[[1. 2. 3. 4.]
[5. 6. 7. 8.]]
```

I like this!

## Array manipulation

Generally, I find many grammars are ugly, but I have to endure it!

### Reshaping

• Example:
```a = np.arange(8).reshape(2, 4)
print(a)
```

gives

```[[0 1 2 3]
[4 5 6 7]]
```

### Indexing

• Indexing is 0-based, not 1-based as in Matlab.

### Slicing

• Example:
```a = np.arange(10)
b = a[0 : 10 : 2] # Matlab uses a(1 : 2 : 10)
print(b)
print(a) # changing b affects a
b = 1000 # b is not an array any more! Oh, my god!
print(a) # a is not affected by the above line
print(b) # b is just an integer now
```

gives

```[0 2 4 6 8]
[100   1 100   3 100   5 100   7 100   9]
[100   1 100   3 100   5 100   7 100   9]
1000
```

### Looping

• Looping 1D array
```a = np.arange(4)
for i in a:
print(i)
```

gives

```0
1
2
3
```
• Looping 2D array
```a = np.arange(8).reshape(2, 4)
for i in a:
print(i)
```

gives:

```[0 1 2 3]
[4 5 6 7]
```

### Aliasing

• Example:
```a = np.arange(4)
b = a # b and a are two names of the same object!
b = 100
print(a)
```

gives:

```[100   1   2   3]
```

### Copying

• Now comes the real copying function:
```a = np.arange(10)
b = a.copy() # make a real copy
print(b) # b has the same elements of a but b is not a
b[:] = 100 # b is changed
print(b)
print(a) # a will not be affected by b at all
```

gives:

```[0 1 2 3 4 5 6 7 8 9]
[100 100 100 100 100 100 100 100 100 100]
[0 1 2 3 4 5 6 7 8 9]
```

# Matplotlib

## Introduction

• `matplotlib` is a big package and `pyplot` is a module in matplotlib.
• The standard way to import the `pyplot` module is as follows:
```import matplotlib.pyplot as plt
```
• All of plotting functions usually expect NumPy arrays as inputs. So we also need:
```import numpy as np
```