Python Introduction#
There are three main ways you might want to run python code in:
Executing a python file
Just like running a bash script or any other program: simply save your python code in a file with “.py” extension and run it using the python interpreter
python filename.py
This the most performant way to run scripts, but the less flexible.
Interactive python shell
If you run the
ipython
interpreter, you can type individual lines of python code and interactively see the return value.This is useful to quickly check the return type of a function or the documentation of an object, but it is not suited to write large program in.
Jupyter Notebook
A mixture of the previous two. By starting the
jupyter-notebook
command (after installing it) you can write a file with several sections which can be interactively executed. It is ideal for quick prototyping and testing.
Python installations typically come with a package manager called pip
.
Here are some examples on how to install libraries with pip
:
python -m pip install ipython
python -m pip install pyserial
python -m pip install python-can
Let’s write some python code#
Basic I/O#
Hello world in python:
print("Hello, World!")
Hello, World!
print
is a global function, so it can be called from any point of the code and it will print some string on the standard output.
Now something more complex:
#!/usr/bin/env python3
# Request the user name from standard input
user_name = input("Enter your name: ")
print("Hello,", user_name)
Show code cell output
---------------------------------------------------------------------------
StdinNotImplementedError Traceback (most recent call last)
Cell In[2], line 4
1 #!/usr/bin/env python3
2
3 # Request the user name from standard input
----> 4 user_name = input("Enter your name: ")
5 print("Hello,", user_name)
File /usr/local/lib/python3.13/site-packages/ipykernel/kernelbase.py:1281, in Kernel.raw_input(self, prompt)
1279 if not self._allow_stdin:
1280 msg = "raw_input was called, but this frontend does not support input requests."
-> 1281 raise StdinNotImplementedError(msg)
1282 return self._input_request(
1283 str(prompt),
1284 self._parent_ident["shell"],
1285 self.get_parent("shell"),
1286 password=False,
1287 )
StdinNotImplementedError: raw_input was called, but this frontend does not support input requests.
First, any line stat starts with a #
is a comment, and is not executed by the python interpreter.
The first line is an Unix “Shebang”, it’s useful on non-windows operating systems to tell the shell which interpreter to use when the file is executed as a program (if the executable flag is enabled).
In the line user_name = input()
, we are initializing the variable user_name
with a string read from the terminal. In python, we don’t need to declare variables before using them. Also, since we are not inside a function or a class, the variable user_name
is global.
Finally, we output a greeting to the user, showing that the function print
accepts multiple arguments.
Important: even though we don’t need to declare variables and their types like in C++/Java, variables still have a type. We can check the type of a variable with the type
function:
print(type(3))
<class 'int'>
print(type('Hello'))
<class 'str'>
print(type(3.14))
<class 'float'>
Math#
Next, let’s see some mathematical operations. Python syntax for math is similar to C.
a = 3 + 6
print("a =", a)
a = 9
a -= 5
print("a =", a)
a = 4
a++ # Invalid python code!
Cell In[8], line 1
a++ # Invalid python code!
^
SyntaxError: invalid syntax
print(3 / 2)
1.5
print(4 / 2)
print(type(4 / 2)) # Careful: a division always returns a float
2.0
<class 'float'>
print(3 // 2) # To cast the output of a division to the int of the floor, simply use //
print(type(3 // 2))
1
<class 'int'>
We can use all the typical bitwise operations from C. The hex
function converts a number into its hexadecimal string representation
a = 0x12345678
a = (a & 0xffff0000) >> 16 | (a & 0x0000ffff) << 16
a = (a & 0xff00ff00) >> 8 | (a & 0x00ff00ff) << 8
a = (a & 0xf0f0f0f0) >> 4 | (a & 0x0f0f0f0f) << 4
a = (a & 0xcccccccc) >> 2 | (a & 0x33333333) << 2
a = (a & 0xaaaaaaaa) >> 1 | (a & 0x55555555) << 1
print(hex(a))
# What did this code do?
0x1e6a2c48
However, always remember that integers in python can be infinitely long (as long as there is memory on your system!)
a = 0xcafebabe << 128
print(hex(a))
0xcafebabe00000000000000000000000000000000
So if you want to simulate uint32_t
from C, simply use & 0xffffffff
print(hex(0xdeadbeef << 8 & 0xffffffff))
print(hex(-1 & 0xffffffff))
0xadbeef00
0xffffffff
There are also some useful global math functions
print(abs(-123))
print(max(1, 2, 3))
print(min(1, 2, 3))
123
3
1
But for more math functions, import the math module
import math
print(math.ceil(1.2))
print(math.pi)
print(math.log(100, 10))
print(math.log(1000, 10)) # This highlights that decimal numbers are 64-bit double precision floats
2
3.141592653589793
2.0
2.9999999999999996
Lists#
python includes has some useful data structures.
Lists can be thought of as arrays from C, but they can contains objects of different types and can be extended and shrunk after their creation
a = [1, 2, "Hello", "World"]
print(a)
[1, 2, 'Hello', 'World']
print(type(a))
<class 'list'>
print(len(a)) # Query the length of the list
4
print(a[2]) # Check what is in the third position
Hello
print(a.pop()) # removes element from the end of the list, like a stack
World
print(a)
[1, 2, 'Hello']
a.append(4) # appends element to the end of the list
print(a)
[1, 2, 'Hello', 4]
print(a.pop(2)) # Remove element from specified location
Hello
print(a)
[1, 2, 4]
a.insert(2, 'Three') # Insert element at specified location
print(a)
[1, 2, 'Three', 4]
a[2] = 3 # Replace element at specified location
print(a)
[1, 2, 3, 4]
a = [9, 1, 5, 3, 2, 5, 1, 6]
print(a[2:5]) # we can extract a subsed of a list like this
[5, 3, 2]
print(a[-1]) # Or just the last element
6
print(a[1::2]) # Every second element, starting from the second
[1, 3, 5, 6]
print(a[::-1]) # Invert the order
[6, 1, 5, 2, 3, 5, 1, 9]
print(1 in a) # Ask if the list contains an element of value 1
True
For a complete list of the methods included in list objects, simply execute help(list)
in ipython.
Tuples#
Tuples are similar to lists, but they can not be modified after their creation
t = (1, 'Two', math.pi)
print(t)
print(type(t))
(1, 'Two', 3.141592653589793)
<class 'tuple'>
print(t[1])
Two
# All these statements will not work
t.pop()
t[0] = 1
t.append(4)
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[35], line 2
1 # All these statements will not work
----> 2 t.pop()
3 t[0] = 1
4 t.append(4)
AttributeError: 'tuple' object has no attribute 'pop'
However, we can create a list out of a tuple, modify it, then create a new tuple from the modified list:
a = list(t)
a.append(4)
t = tuple(a)
print(t)
(1, 'Two', 3.141592653589793, 4)
Tuples can be used to assign multiple variables in one statement:
t = (1, 'pasta', 12.1)
a, b, c = t
print("a is", a)
print("b is", b)
print("c is", c)
a is 1
b is pasta
c is 12.1
Dictionaries#
Next we have dictionaries. Dictionaries are like hash maps in other languages:
d = {} # Initialize an empty dictionary
print(d)
print(type(d))
{}
<class 'dict'>
# The keys and values of a dictionary can be any type
d['name'] = 'Enrico'
d['age'] = 31
d[5] = 3.14
print(d)
{'name': 'Enrico', 'age': 31, 5: 3.14}
del d[5] # Remove the element with key 5 from the dictionary
print(d)
{'name': 'Enrico', 'age': 31}
print(list(d.keys())) # We can get the list of the keys of a dictionary
['name', 'age']
print(list(d.values()))
['Enrico', 31]
print(list(d.items()))
[('name', 'Enrico'), ('age', 31)]
Sets#
Finally, we have sets. Sets are similar to list, but they don’t allow duplicates
greetings = {'Hello', 'Hi'}
print(greetings)
print(type(greetings))
{'Hi', 'Hello'}
<class 'set'>
greetings.add('Good Morning')
print(greetings)
{'Hi', 'Hello', 'Good Morning'}
greetings.add('Hi')
print(greetings)
{'Hi', 'Hello', 'Good Morning'}
strings#
Strings are immutable arrays of characters which are already encoded:
s = 'Hello, World!'
print(s)
print(type(s))
Hello, World!
<class 'str'>
s[1] = a # Replacing a single character does not work
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/home/jonas/Documents/knowledgebase/dissecto_kb/chapters/python_intro.ipynb Cell 72 line 1
----> <a href='vscode-notebook-cell:/home/jonas/Documents/knowledgebase/dissecto_kb/chapters/python_intro.ipynb#Y243sZmlsZQ%3D%3D?line=0'>1</a> s[1] = a # This
TypeError: 'str' object does not support item assignment
s = "Viel Spaß!"
print(s)
Viel Spaß!
s = "😁😛😋🤣"
print(s)
😁😛😋🤣
print(len(s))
4
print(s[2])
😋
# Each character is a string object itself!
print(type(s[2]))
<class 'str'>
s = "Hello, World!"
print(ord(s[1])) # ord converts character to ASCII code (or Unicode)
101
print(chr(101)) # chr does the inverse
e
a = "Hello, "
b = "World!"
print(a + b) # String concatenation
Hello, World!
print("+-" * 20) # String repetition
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
s = "Hello, World!"
print(s[::-1]) # String inversion
!dlroW ,olleH
# Sprintf-like string formatting using %
s = "%d + %d = 0x%x" % (100, 60, 160)
print(s)
100 + 60 = 0xa0
bytes#
Perhaps one of the most confusing elements of python 3 for novices are byte string (bytes
).
They function like strings but they can only contain bytes from 0 to 255, no unicode characters!
bytes
are typically used for buffers for binary communication and binary files.
We can declare bytes strings in several different ways:
a = b"Hello, World!"
print(a)
b = bytes([6, 32, 54, 13])
print(b)
c = bytes.fromhex("deadbeef")
print(c)
b'Hello, World!'
b'\x06 6\r'
b'\xde\xad\xbe\xef'
By default, when printing a bytes string, python will try to display it as an escaped C string.
Sometimes this is not convenient, so we can convert it to an hexadecimal string representation using the hex() method:
b = bytes([6, 32, 54, 13])
print(b)
print(b.hex())
b'\x06 6\r'
0620360d
We can not initialize a byte string with an unescaped non-ascii character
b0 = b"Nils Weiß"
Cell In[61], line 1
b0 = b"Nils Weiß"
^
SyntaxError: bytes can only contain ASCII literal characters
But we can encode strings to bytes and decode bytes to strings:
s = "Nils Weiß"
b0 = s.encode('latin1')
print(type(b0))
print(b0)
<class 'bytes'>
b'Nils Wei\xdf'
b1 = s.encode('utf-8')
print(b1)
b'Nils Wei\xc3\x9f'
print(b1.decode('utf-8'))
Nils Weiß
Flow control#
Python has if-elif-else statements, but no switch statements.
Instead of a switch, we can create many elif branches:
user_string = input("Give me a number: ")
number = float(user_string)
if number % 2 == 1:
print("The number was odd")
elif number % 2 == 0:
print("The number was even")
else:
print("The number was neither odd nor even")
Give me a number: 4
The number was even
We can combine boolean conditions with and
, or
and not
:
year = 2000
if (year % 4 == 0) and not (year % 100 == 0) or (year % 400 == 0):
print("%d is a leap year" % year)
2000 is a leap year
We have two loop types: for
and while
. No do..while
.
While loops are easy and work as they work in C, Java and others:
elements = []
stop = False
while not stop:
user_string = input("Enter a number or an empty string to stop")
if user_string == "":
stop = True
else:
elements.append(user_string)
print(elements)
Enter a number or an empty string to stop
[]
In python, a for
loop iterates over iterable objects (such as lists, strings…)
for a in [1, 12, 'Blue']:
print(a)
1
12
Blue
The global function range
generates an iterable range of numbers:
for i in range(5):
print(i)
0
1
2
3
4
Exceptions are also possible, and they work similarly to C++/Java:
try:
user_string = input("Give a number: ")
number = int(user_string)
except Exception as ex:
print("The following Exception happened ", ex)
else: # Optional: only executed if no exception happened
print("No exception happened, all good!")
finally: # Optional: always executed, used for cleanup
print("Processing finished, doing some cleanup")
Give a number: 5234
No exception happened, all good!
Processing finished, doing some cleanup
Some objects that require cleanup can be created in a with
statement, to guarantee they get disposed correctly:
with open("file.txt", "w") as fd:
fd.write("Hello, World!")
# fd is guaranteed to be flushed and disposed correctly after the with block
Functions#
Functions are declared as follows:
def add_numbers(a, b):
answer = a + b
return answer
print(add_numbers(1, 3))
4
You can have optional arguments by assigning the default value in the argument declaration:
def greet(username='User'):
print("Hello,", username)
greet()
greet("Enrico")
Hello, User
Hello, Enrico
Functions are first class citizens.
For example, the list.sort
method takes a function as the optional argument key
def invert_number(n):
return -n
a = [5, 4, 1, 2, 3]
a.sort()
print("Sorted list:", a)
a.sort(key=invert_number)
print("List sorted from largest to smallest:", a)
Sorted list: [1, 2, 3, 4, 5]
List sorted from largest to smallest: [5, 4, 3, 2, 1]
Anonymous functions can be generated inline, but only if they are composed of a single statement:
a = [4, 5, 1, 2, 3]
a.sort(key=lambda n: -n)
print("List sorted from largest to smallest:", a)
List sorted from largest to smallest: [5, 4, 3, 2, 1]
Functions can be nested and they automatically capture the variables from the outer scope:
def make_greeter(name):
def greeter():
print("Hello,", name)
# Return a "function pointer" that includes the captured name
return greeter
greeter1 = make_greeter("Enrico")
greeter2 = make_greeter("Nils")
greeter1()
greeter2()
Hello, Enrico
Hello, Nils
Classes#
class Package:
def __init__(self, weight, width, height, depth):
# This is the constructor
# We declare all the local variables here
self.weight = weight
self.size = (width, height, depth)
self.destination_city = None # None is like null in Java/C++
self.destination_address = None
def volume(self):
# Instance methods need to have "self" as a first argument
width, height, depth = self.size
return width * height * depth
def density(self):
# Note that all the object fields and methods are accessed from `self`
return self.weight / self.volume()
def send_to(self, city, address):
self.destination_city = city
self.destination_address = address
# Instantiate the class: like C++/Java, but without the `new` keyword
p = Package(520, 5, 4, 12)
p.send_to("Regensburg", "Franz-Mayer-Strasse 1")
print("Volume of the package is", p.volume())
print("Density of the package is", p.density())
print("Destination city is", p.destination_city)
Volume of the package is 240
Density of the package is 2.1666666666666665
Destination city is Regensburg
class InternatioinalPackage(Package): # Inheritance
def __init__(self, weight, width, height, depth):
# Call the parent constructor statically, we need to provide "self"
Package.__init__(self, weight, width, height, depth)
self.destination_contry = None
def send_internationally(self, country, city, address):
self.send_to(city, address)
self.destination_country = country
p = InternatioinalPackage(520, 5, 4, 12)
p.send_internationally("Italy", "Padova", "Via G. Gradenigo 6/b")
print("Volume of the package is", p.volume())
print("Density of the package is", p.density())
print("Destination country is", p.destination_country)
Volume of the package is 240
Density of the package is 2.1666666666666665
Destination country is Italy
Outside of __init__
, there are several other special method names to override specific behaviors.
Here are some examples:
def __del__(self)
is the destructor, called when the object is deallocated by the GCdef __add__(self, other)
overrides additiondef __getitem__(self, key)
overrides theobject[key]
indexing operator__enter__
and__exit__
override the behavior of a entering and leaving awith
block
Advanced python syntax#
Ternary operator#
Python has a ternary operator:
value = value_if_true if condition else value_if_false
It is equivalent to the following C code:
value = condition ? value_if_true : value_if_false
a = 1
print(True if a else False)
True
List comprehension#
We can create a new list applying one operation to every element of an existing list:
a = [3, 2, 6, 9, 2]
doubles = [ n * 2 for n in a ]
print(doubles)
[6, 4, 12, 18, 4]
We can also place a condition on which elements should be included in the list:
a = [3, 2, 6, 9, 2]
odds = [ n for n in a if n % 2 == 1 ]
print(odds)
[3, 9]
Notice the similarities to SQL queries
SELECT users.name
FROM users
WHERE users.age >= 18
result = [
user.name
for user in users
if user.age >= 18
]