x = 10
y = 4
print(x, y)10 4
Python is a widely used programming language with a rich ecosystem of libraries.
. . .
Many modern machine learning and deep learning frameworks—such as PyTorch and TensorFlow—are built around Python.
. . .
In this tutorial, we’ll walk through the essentials of Python programming and help you get set up with a clean, reproducible workflow. No prior experience is required.
We will use uv (Astral) to manage Python environments and dependencies.
It is fast, lightweight, and makes projects easy to reproduce across machines.
uvFollow the official installation guide:
MacOS and Linux:
curl -LsSf https://astral.sh/uv/install.sh | shWindows:
powershell -ExecutionPolicy ByPass -c
\"irm https://astral.sh/uv/install.ps1 | iex\"Navigate to your working directory.
Initialize a new project.
uv initThis will create a minimal project structure along with a pyproject.toml.
Add packages to your project (and lock them):
uv add <package_name>For example:
uv add numpy pandas matplotlibUsing Python: Run a Python script within the project environment:
uv run example.pyUsing Jupyter Notebook: If you want to use Jupyter notebooks, install Jupyter inside the project environment:
uv add jupyterThen start Jupyter from within the project directory:
uv run jupyter labIn most setups, you should see a kernel associated with your project’s .venv.
To reproduce the exact same environment on another machine:
pyproject.toml and uv.lock to the new location.uv syncuv sync is case-sensitive so make sure to use lowercase.
Common choices:
If you’re new to Python, VS Code or PyCharm are the easiest places to start.
It’s easy to assign values to variables and print them.
x = 10
y = 4
print(x, y)10 4
And it’s a good practice to add comments to your code.
# Comments start with hash #print(f"Addition: x+y = {x+y}")
print(f"Power: x**y = {x**y}")
print(f"Integer Division: x//y = {x//y}")
print(f"Division: x/y = {x/y}")
concat = str(x) + "+" + str(y)
print(f"Concatenation: {concat}")Addition: x+y = 14
Power: x**y = 10000
Integer Division: x//y = 2
Division: x/y = 2.5
Concatenation: 10+4
We can also use loops to perform repetitive calculations.
for i in range(5):
print(f"Loop: i = {i}")Loop: i = 0
Loop: i = 1
Loop: i = 2
Loop: i = 3
Loop: i = 4
We use a format called f-strings to format the output. For example, f"Loop: i = {i}" will replace {i} with the value of i.
Python includes several built-in container types: lists, dictionaries, sets, and tuples. We will take a look at them.
A list is the Python equivalent of an array, but is resizeable and can contain elements of different types.
. . .
xs = [3, 1, 2]
print(xs, xs[2])[3, 1, 2] 2
. . .
Change the contents of a list (lists can contain elements of different types):
xs[2] = "foo"
print(xs)[3, 1, 'foo']
Add a new element to the end of a list:
xs.append("bar")
print(xs)[3, 1, 'foo', 'bar']
. . .
Remove and return the last element from the list:
x = xs.pop()
print(x, xs)bar [3, 1, 'foo']
In addition to accessing list elements one at a time, Python provides concise syntax to access sublists. This is known as slicing.
nums = list(range(5))
print(nums)[0, 1, 2, 3, 4]
. . .
print(nums[2:4])
print(nums[2:])[2, 3]
[2, 3, 4]
You can loop over the elements of a list:
animals = ["cat", "dog", "monkey"]
for animal in animals:
print(animal)cat
dog
monkey
If you want access to the index of each element within the body of a loop, use the built-in enumerate function:
animals = ["cat", "dog", "monkey"]
for idx, animal in enumerate(animals):
print(f"#{idx + 1}: {animal}")#1: cat
#2: dog
#3: monkey
Frequently we want to transform one type of data into another. As a simple example, consider computing square numbers.
First, using a loop:
nums = [0, 1, 2, 3, 4]
squares = []
for x in nums:
squares.append(x**2)
print(squares)[0, 1, 4, 9, 16]
Now, using a list comprehension:
nums = [0, 1, 2, 3, 4]
print([x**2 for x in nums])[0, 1, 4, 9, 16]
. . .
List comprehensions can also contain conditions:
nums = [0, 1, 2, 3, 4]
print([x**2 for x in nums if x % 2 == 0])[0, 4, 16]
A dictionary stores (key, value) pairs. Create and use a dictionary:
d = {"cat": "cute", "dog": "furry"}
print(d["cat"])
print("cat" in d)cute
True
print(d['cat']) gets an entry from the dictionary (“cute”).
'cat' in d checks if the key exists.
Add a new key-value pair to the dictionary:
d["fish"] = "wet"
print(d["fish"])wet
. . .
Deleting keys and using get with a default:
del d["fish"]
print(d.get("fish", "N/A"))N/A
Loops: Iterate over the keys in a dictionary:
d = {"person": 2, "cat": 4, "spider": 8}
for animal in d:
legs = d[animal]
print(f"A {animal} has {legs} legs")A person has 2 legs
A cat has 4 legs
A spider has 8 legs
items: If you want access to keys and their corresponding values, use the items method:
d = {"person": 2, "cat": 4, "spider": 8}
for animal, legs in d.items():
print(f"A {animal} has {legs} legs")A person has 2 legs
A cat has 4 legs
A spider has 8 legs
Dictionary comprehensions: You can construct dictionaries easily with comprehensions:
nums = [0, 1, 2, 3, 4]
even_num_to_square = {x: x**2 for x in nums if x % 2 == 0}
print(even_num_to_square){0: 0, 2: 4, 4: 16}
A set is an unordered collection of distinct elements.
Create a set and see how it behaves:
animals = {"cat", "dog"}
print("cat" in animals)
print("fish" in animals)True
False
Add a new element to the set:
animals.add("fish")
print("fish" in animals)
print(len(animals))True
3
Sets automatically avoid duplicates.
animals.add("cat")
print(len(animals))
animals.remove("cat")
print(len(animals))3
2
Loops: Iterate over a set:
animals = {"cat", "dog", "fish"}
for idx, animal in enumerate(animals):
print(f"#{idx + 1}: {animal}")#1: cat
#2: dog
#3: fish
Set comprehensions: Construct sets using comprehensions:
from math import sqrt
nums = {int(sqrt(x)) for x in range(30)}
print(nums){0, 1, 2, 3, 4, 5}
A tuple is an (immutable) ordered list of values. Tuples can be used as keys in dictionaries and as elements of sets, while lists cannot.
d = {(x, x + 1): x for x in range(10)}
t = (5, 6)
print(f"Type of t: {type(t)}")
print(f"Value of d[t]: {d[t]}")
print(f"Value of d[(1, 2)]: {d[(1, 2)]}")Type of t: <class 'tuple'>
Value of d[t]: 5
Value of d[(1, 2)]: 1
Python functions are defined using the def keyword.
For example, define the sign function as:
\[\text{sign}(x) = \begin{cases}
1 & \text{if } x > 0 \\
0 & \text{if } x = 0 \\
-1 & \text{if } x < 0
\end{cases}\]
def sign(x):
if x > 0:
return 1
elif x < 0:
return -1
else:
return 0Print the result for sample values:
for x in [-1, 0, 1]:
print(f"sign({x}) = {sign(x)}", end=", ")sign(-1) = -1, sign(0) = 0, sign(1) = 1,
We will often define functions to take optional keyword arguments, like this:
Define a function that prints a greeting, with an optional keyword argument loud:
def hello(name, loud=False):
if loud:
print(f"HELLO, {name.upper()}!")
else:
print(f"Hello, {name}")Print the result for sample values:
hello("Bob")
hello("Fred", loud=True)Hello, Bob
HELLO, FRED!
You will see various functions in real applications.
You can install NumPy in your project using uv:
uv add numpyAfter the packages have been successfully installed, we can call particular functions from the package in our codes.
Before we use these functions, we should first import them at the beginning of our codes.
There are several ways to import package/modules.
Import numpy under an alias:
import numpy as npImport specific submodules/functions:
from numpy import linalg as la, dot as matrix_multiplyBe careful when you use alias. It may result in namespace collisions.
numpy.np.ndarray, which we will use to represent matrix/vector for computations.Construct numpy arrays:
x = np.array([1, 2, 3])
y = np.array([[3, 4, 5]])
print(x, y)[1 2 3] [[3 4 5]]
z = np.array([[6, 7], [8, 9]])
print(z)[[6 7]
[8 9]]
Print the shape of arrays:
print(x.shape)
print(y.shape)
print(z.shape)(3,)
(1, 3)
(2, 2)
In general, NumPy has similar indexing as Python.
There are also indexing methods specific to NumPy.
Mastery of indexing helps write efficient NumPy codes.
For example, avoid explicit for-loops over indices because for-loops will dramatically slow down the code.
Create a random (3,4) matrix:
p = np.random.random((3, 4))Select everything in p:
p[:]array([[6.30433758e-02, 9.79667986e-01, 5.35072214e-01, 6.38703746e-01],
[8.91027616e-01, 1.41303193e-04, 1.28977947e-01, 8.55965255e-01],
[1.62228924e-01, 6.04969594e-01, 8.61153080e-01, 9.25238156e-01]])
Select the 0th and 2nd rows:
p[np.array([0, 2]), :]array([[0.06304338, 0.97966799, 0.53507221, 0.63870375],
[0.16222892, 0.60496959, 0.86115308, 0.92523816]])
Select 1st row as 1-D vector and 1st through 2nd elements:
p[1, 1:3]array([0.0001413 , 0.12897795])
Boolean indexing:
p[p > 0.5]array([0.97966799, 0.53507221, 0.63870375, 0.89102762, 0.85596526,
0.60496959, 0.86115308, 0.92523816])
Create 3-D vector of shape(3,4,1) from p:
p[:, :, np.newaxis]array([[[6.30433758e-02],
[9.79667986e-01],
[5.35072214e-01],
[6.38703746e-01]],
[[8.91027616e-01],
[1.41303193e-04],
[1.28977947e-01],
[8.55965255e-01]],
[[1.62228924e-01],
[6.04969594e-01],
[8.61153080e-01],
[9.25238156e-01]]])
We can apply matrix operations to these objects just like linear algebra.
There are many advanced functions which are very useful in SciPy and np.linalg.
We will just show some of the most common operations below.
Create two arrays for operations:
x = np.array([[1, 2], [3, 4]], dtype=np.float64)
y = np.array([[5, 6], [7, 8]], dtype=np.float64)Elementwise sum:
print(x + y)
print(np.add(x, y))[[ 6. 8.]
[10. 12.]]
[[ 6. 8.]
[10. 12.]]
Elementwise difference:
print(x - y, np.allclose(x - y, np.subtract(x, y)))[[-4. -4.]
[-4. -4.]] True
Elementwise product:
print(x * y, np.allclose(x * y, np.multiply(x, y)))[[ 5. 12.]
[21. 32.]] True
Dot product & matrix multiplication:
print(np.dot(x, y), np.allclose(np.dot(x, y), x @ y))[[19. 22.]
[43. 50.]] True
Unlike MATLAB, * is elementwise multiplication, not matrix multiplication. We instead use the dot function to compute inner products of vectors, to multiply a vector by a matrix, and to multiply matrices.
Elementwise division:
print(x / y, np.allclose(x / y, np.divide(x, y)))[[0.2 0.33333333]
[0.42857143 0.5 ]] True
Elementwise square root:
print(np.sqrt(x))[[1. 1.41421356]
[1.73205081 2. ]]
Matrix Operations (Norm):
print(np.linalg.norm(y))13.19090595827292
Transpose:
print(z.T)[[6 8]
[7 9]]
Sum Operation: To compute sum of all elements, each column, or each row:
x = np.array([[1, 2], [3, 4]])
print(np.sum(x)) # sum of all elements
print(np.sum(x, axis=0)) # sum of each column
print(np.sum(x, axis=1)) # sum of each row10
[4 6]
[3 7]
Broadcasting is a powerful mechanism that allows numpy to work with arrays of different shapes when performing arithmetic operations.
Frequently we have a smaller array and a larger array, and we want to use the smaller array multiple times to perform some operation on the larger array.
Suppose that we want to add a constant vector to each row of a matrix. We could do it like this:
Adding a vector to each row with an explicit loop:
x = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])
v = np.array([1, 0, 1])
y = np.empty_like(x)
for i in range(4):
y[i, :] = x[i, :] + v
print(y)[[ 2 2 4]
[ 5 5 7]
[ 8 8 10]
[11 11 13]]
This works; however, when the matrix x is very large, computing an explicit loop in Python could be slow.
Note that adding the vector v to each row of the matrix x is equivalent to forming a matrix vv by stacking multiple copies of v vertically.
Then performing elementwise summation of x and vv.
Stack multiple copies of a vector and add:
x = np.array(
[
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10, 11, 12],
]
)
v = np.array([1, 0, 1])Stack 4 copies of v on top of each other:
vv = np.tile(v, (4, 1))
print(f"vv={vv}")vv=[[1 0 1]
[1 0 1]
[1 0 1]
[1 0 1]]
Add x and vv elementwise:
y = x + vv
print(f"y={y}")y=[[ 2 2 4]
[ 5 5 7]
[ 8 8 10]
[11 11 13]]
Numpy broadcasting allows us to perform this computation without actually creating multiple copies of v.
Using broadcasting to add a vector to each row:
x = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])
v = np.array([1, 0, 1])
y = x + v # Add v to each row of x using broadcasting
print(y)[[ 2 2 4]
[ 5 5 7]
[ 8 8 10]
[11 11 13]]
The line y = x + v works even though x has shape (4, 3) and v has shape (3,) due to broadcasting;
This line works as if v actually had shape (4, 3), where each row was a copy of v, and the sum was performed elementwise.
1s until both shapes have the same length.1 and the other array had size greater than 1, the first array behaves as if it were copied along that dimensionMore applications of broadcasting:
Compute outer product of vectors:
v = np.array([1, 2, 3]) # v has shape (3,)
w = np.array([4, 5]) # w has shape (2,)
print(np.reshape(v, (3, 1)) * w)[[ 4 5]
[ 8 10]
[12 15]]
Here v has shape (3,) and w has shape (2,), so the outer product has shape (3, 2).
To compute an outer product, we first reshape v to be a column vector of shape (3, 1); we can then broadcast it against w to yield an output of shape (3, 2), which is the outer product of v and w.
Add a vector to each row of a matrix:
x = np.array([[1, 2, 3], [4, 5, 6]])
print(x + v)[[2 4 6]
[5 7 9]]
Here x has shape (2, 3) and v has shape (3,), so they broadcast to (2, 3)
Add a vector to each column of a matrix: Here x has shape (2, 3) and w has shape (2,).
print((x.T + w).T)
print(x + np.reshape(w, (2, 1)))[[ 5 6 7]
[ 9 10 11]]
[[ 5 6 7]
[ 9 10 11]]
x then it has shape (3, 2) and can be broadcast against w to yield a result of shape (3, 2);x with the vector w added to each column.w to be a column vector of shape (2, 1); we can then broadcast it directly against x to produce the same output.Multiply a matrix by a constant: Here x has shape (2, 3). Numpy treats scalars as arrays of shape (), these can be broadcast together to shape (2, 3), producing the following array:
print(x * 2)[[ 2 4 6]
[ 8 10 12]]
Matplotlib is a plotting library. In this section we give a brief introduction to the matplotlib.pyplot module, which provides a plotting system similar to MATLAB.
The most important function in matplotlib is plot, which allows you to plot 2D data. Here is a simple example:
matplotlib.pyplot as plt and numpy as np.plt.show() to make the graphic appear.import matplotlib.pyplot as plt
import numpy as np
x = np.arange(0, 3 * np.pi, 0.1)
y = np.sin(x)
plt.plot(x, y)
plt.title("Sine Curve")
plt.show()With just a little bit of extra work, we can easily plot multiple lines at once, and add a title, legend, and axis labels.
Example: Plotting sine and cosine curves with axis labels and legend
x = np.arange(0, 3 * np.pi, 0.1)
y_sin = np.sin(x)
y_cos = np.cos(x)
plt.plot(x, y_sin)
plt.plot(x, y_cos)
plt.xlabel("x axis label")
plt.ylabel("y axis label")
plt.title("Sine and Cosine")
plt.legend(["Sine", "Cosine"])
plt.show()Sometimes we want multiple subplots in one figure.
Example: Generate 6 histograms in a grid
3 x 2 grid of subplots.m and standard deviation s.from numpy.random import uniform, normal as norm
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 3)
for i in range(2):
for j in range(3):
m, s = uniform(-1, 1), uniform(1, 2)
x = norm(loc=m, scale=s, size=100)
axes[i, j].hist(x, alpha=0.6, bins=20)
t = rf"$\mu = {m:.2f}, \quad \sigma = {s:.2f}$"
axes[i, j].set(title=t, xticks=[-4, 0, 4])Matplotlib supports 3D plots.
Example: 3D surface plot
from mpl_toolkits.mplot3d.axes3d import Axes3D
from matplotlib import cm
def f(x, y):
return np.cos(x**2 + y**2) / (1 + x**2 + y**2)
xgrid = np.linspace(-3, 3, 50)
ygrid = xgrid
x, y = np.meshgrid(xgrid, ygrid)fig = plt.figure(figsize=(10, 6))
ax = fig.add_subplot(111, projection="3d")
ax.plot_surface(
x,
y,
f(x, y),
rstride=2,
cstride=2,
cmap=cm.jet,
alpha=0.7,
linewidth=0.25,
)
ax.set_zlim(-0.5, 1.0)The tutorial above is mainly from
I strongly recommend these materials for you to get started.
There are more about Python to follow during practice. Here are some materials for your reference. From my personal experience, official documentations are often the most useful guides. StackOverflow and Blogs also help a lot.