This week's Jupyter notebook introduces us to basic data types and actions in Python.
Without further ado, let's start:
# Heads-up: This is the syntax for writing comments in Python.
# Start a line with the "#" character to turn it into a comment.
# Comments are ignored by the interpreter, so you can use them for annotation purposes
# You can use the print() function to display simple data types such as numbers and strings.
print("Hello, world!") # here
Hello, world!
# Initialize a variable called "a" with a value of 5
a = 5
print(a)
5
# We don't have to use print() each time in notebook cells or using the interactive interpreter.
# Just using the name of a variable will also display its value:
a
5
Assigning a number this way will create a variable of type int
. If unsure, one can always check the type of a variable with the type()
built-in method.
type(a)
int
# We can change the value of a variable as follows
a = 100
a
100
b = 200
# Addition is as simple as
a + b
300
b = b * 2
print(b)
6400
b
200
# If we want to save the result of an expression we can assign it to a variable
c = a * b - 10
c
639990
Aside from the four basic arithmetic operators +
, -
, *
, /
, there exist other operators in Python as well.
One such example is the power operator (**
), where a ** b
can be used for calculating $a^b$:
a ** 5
10000000000
Note: The int
type is flexible in terms of representing relatively huge numbers:
a ** 5.2
25118864315.09582
type(a)
int
We can also represent floating-point numbers just as easily:
a = 5.0
type(a)
float
The following syntax also works for representing float
types:
5.
5.0
.5
0.5
Here's an example of a complex
type, which is specified by having the j
suffix after the imaginary part of the number:
c = 3 + 4j
type(c)
complex
As an example, this type comes with its own multiplication operator:
c * c
(-7+24j)
(Note: we learn about libraries more in detail, later...)
Some useful functions, such as sin()
and cos()
, are not directly available:
cos(0)
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-22-8d215ebe8f10> in <module> ----> 1 cos(0) NameError: name 'cos' is not defined
To be able to use these functions, we have to import
a library that contains them. Such useful math functions can be accessed by importing the math
library, as follows:
import math
To use a function from a library, we use the lname.fname(...)
syntax, where lname
represents the imported library name and fname
represents the function name.
As an example:
math.cos
<function math.cos(x, /)>
math.pow(2, 5)
32.0
Aside from functions, can also provide special values such as pi:
math.cos(math.pi)
-1.0
We can define variables to have the Boolean values of True
and False
to do logical operations.
a = True
b = False
The and
binary operator evaluates to True
if both of its operands are True
, otherwise it evaluates to False
.
a and b
False
b and b
False
a and a
True
The or
binary operator evalues to True
if any of its operands are True
, otherwise it evaluates to False
.
a or b
True
b or b
False
a or a
True
not
negates the value of a Boolean variable.
not True
False
not False
True
Comparison operators (such as >
, <
, >=
, <=
, ==
, !=
) will return Boolean values:
a = 10
# Equality
a == 10
True
3 > 5
False
10 == 10
True
10 = 10
File "<ipython-input-41-891d8cbaac36>", line 1 10 = 10 ^ SyntaxError: cannot assign to literal
# Inequality
3 != 3.1
True
Python gives us many types for storing data. We'll explore each one, starting from strings:
Strings represent a sequence of characters, and are created with using enclosing single (') or double (") quotation marks:
s = 'hello'
type(s)
str
Python allows us to do several operations with indexes, one such example being concatenation:
d = 'hello'
e = 'world'
d + e
'helloworld'
# If we want a space between the words, we can do this:
d + ' ' + e
'hello world'
Although both numbers and strings are able to use +
, Python will not allow mixing integers with strings:
10 + '10'
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-48-510d8142d9e2> in <module> ----> 1 10 + '10' TypeError: unsupported operand type(s) for +: 'int' and 'str'
To convert a string into a number, we can use int()
:
a = '10'
10 + int(a)
20
We can also check if a string contains another string inside it, using the in
keyword:
'hello' in 'hello, world'
True
's' in 'sk'
True
'skibidi' not in 'hello, world'
True
Strings are case sensitive:
'hello' == 'Hello'
False
The len()
function allows us to get the length of a string:
len('Hello')
5
# Don't forget, each character (including spaces and punctuation) contributes to the length:
len('Hello, world!')
13
Strings and other sequential containers allow repetition with *
:
'hello' * 3
'hellohellohello'
Sequential containers such as strings and lists allow us to access elements using their position (index) in the container. Indexing and slicing use square brackets []
.
Note 1: In python, indexes start at 0. This means that the first character you see in the string has index 0, the second character has index 1, and so on.
Note 2: Python allows negative indexing. Negative indexes start by counting from the end of the string.
It's best to proceed with a simple visualization of a string (ceng240) along with its positive and negative indexes:
0 | 1 | 2 | 3 | 4 | 5 | 6 | Positive indexing |
---|---|---|---|---|---|---|---|
c | e | n | g | 2 | 4 | 0 | |
-7 | -6 | -5 | -4 | -3 | -2 | -1 | Negative indexing |
For example, the 'g' element can be accessed via positive indexing (3), as well as negative indexing (-4).
s = 'ceng240'
Simple indexing has the syntax of container[idx]
, which will access the element with the given index. Here are some examples with positive indexing:
# The "first" character has the index 0
s[0]
'c'
s[3]
'g'
s[5]
'4'
s[len(s) - 1]
'0'
# To access the last element, we can use
s[len(s) - 1]
'0'
len('')
0
Here are some examples with negative indexing:
# First element from the end
s[-1] # not s[-0], as that is just s[0]
'0'
s[-3]
'2'
One can observe that for negative indexing, we have the following equality:
s[-idx] = s[len(s) - idx]
s[-5] == s[len(s) - 5]
True
With the s[idx1:idx2]
syntax, Python allows us to access a slice (i.e, multiple elements) of a container. The slice starts from the first index (inclusive), up to (but not including) the second index parameter.
Some examples of slicing:
s[4]
'2'
s[2:5]
'ng2'
s[3:5]
'g2'
# This will also work (if confused, check the table)
s[-3:-1]
'24'
If idx1
is left empty, the slice will start from the start of the container.
(i.e, by default, idx1 = 0
)
If idx2
is left empty, the slice will continue until the end of the container.
(i.e, by default, idx2 = length of the container
)
s[1:]
'eng240'
# This will skip the initial element
s[1:]
'eng240'
# And this will slice the first 5 - 1 elements of the string
s[:5]
'ceng2'
s[:-1]
'ceng24'
# Get the last 3 characters of the string:
s[-3:]
'240'
# Slice all the characters except the last 4:
s[:-4]
'cen'
(you can add additional cells under this cell to practice your slicing skills)
Finally, Python allows strided indexing, with the s[idx1:idx2:stride]
syntax.
The stride
(or increment) parameter allows the slice to "skip over" consecutive elements of the string. After indexing the first element (with idx1
), the next index will be selected by incrementing (or decrementing!) the current index with stride
, and so on.
It's best to learn by example, as always:
s[len(s)]
--------------------------------------------------------------------------- IndexError Traceback (most recent call last) <ipython-input-76-e1a0b8bd96dc> in <module> ----> 1 s[len(s)] IndexError: string index out of range
# This is the usual behavior of slicing, where the next index is selected by incrementing by 1:
s[::]
'ceng240'
# Now, after selecting the first element, we will select every other element:
s[::2]
'cn20'
s[::4]
'c2'
# This will allow you to get the even numbers
'0123456789'[::2]
'02468'
# To get the odd numbers, you could do this
'0123456789'[1::2]
'13579'
# Classic trick to "invert" a string:
s[::-1]
'042gnec'
# One final example of strided slicing:
'0123456789'[::-3]
'9630'
Leaving the stride parameter empty is the same as setting it to 1:
s[::] == s[::1]
True
The following will not be accepted by Python, because we are trying to use quotation marks inside a string:
# Python will complain about this
j = "I said "hello, world""
File "<ipython-input-73-b46be292b878>", line 2 j = "I said "hello, world"" ^ SyntaxError: invalid syntax
To solve this, we can use single quotes (') to define the string, and use double quotes (") inside the string:
j = 'I said "hello, world"'
j
'I said "hello, world"'
The same technique can be used for using single quotes in a string:
j = "I said 'hello, world'"
j
"I said 'hello, world'"
Alternatively, we can use the backslash (\) character to make Python understand that this character should be inside the string:
# We escape the quotes by using \"
j = "I said \"hello, world\""
j
'I said "hello, world"'
We can make multi-line strings by using the docstring syntax, where the string is enclosed by three single quotation marks ('''text'''
).
k = '''
hello
world
ceng240'''
Note that this will also preserve the lines:
print(k)
hello world ceng240
When we display k
directly, we see the following:
k
'\n hello\n world\n ceng240'
Here, \n
is what Python uses to represent a vertical line, as a special character (called the newline character). We can use it ourselves to print text in different lines as follows:
print("hello\nworld")
hello world
'çağrı'
'çağrı'
Note: Strings are immutable, which means after we create a string, it is not possible to change its content:
s = 'hello'
s[0] = 'j'
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-86-7927831c176f> in <module> 1 s = 'hello' ----> 2 s[0] = 'j' TypeError: 'str' object does not support item assignment
'j' + s[1:]
'jello'
h = ['h', 'e', 'l', 'l', 'o']
h[0] ='j'
h
['j', 'e', 'l', 'l', 'o']
s
{(1, 2), 1, 2, 3, 4, 'hello'}
Here, we would create a new string:
'j' + s[1:]
'jello'
'ceng240'[-5:-2]
'ng2'
'ceng240'[-5:-2:2]
'n2'
a = 8
8 = a
File "<ipython-input-89-2b0f6c8cd52b>", line 1 8 = a ^ SyntaxError: cannot assign to literal
a - 2 = 5
File "<ipython-input-90-52ad62992b17>", line 1 a - 2 = 5 ^ SyntaxError: cannot assign to operator
a = 5 + 2
Lists (and tuples) are sequential containers that can hold data of different types inside. They are defined using square brackets [ ]
.
# Create a list of random stuff
l = ['a', 10, 'c', False]
type(l)
list
[]
[]
l
['a', 10, 'c', False]
l[-1]
False
As we saw with strings, we can do common operations on lists, such as slicing and indexing:
l[1:]
[10, 'c', False]
l[::-1]
[False, 'c', 10, 'a']
Lists can be elements of other lists, as follows:
l = ['1',
2,
'a',
['s', 0, 10]]
# Accessing an element in a list inside a list:
l[3][0]
's'
You could represent a matrix as a list of lists:
a = [
[1,2,3],
[3,4, 5],
[1, 10, 8]
]
a[2][2]
8
Lists are mutable, which means the data inside the container can be changed after initialization:
l = [1, '2', 10, [1], 5]
l[0] = 'hello'
l
['hello', '2', 10, [1], 5]
If we want the data to unchangeable after initialization, we use tuples instead, which are immutable. Tuples use parentheses ()
instead of square brackets.
t = (1, '2', 10, [1], 5)
type(t)
tuple
t[0] = 'hello'
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-95-cf6b9af8b690> in <module> ----> 1 t[0] = 'hello' TypeError: 'tuple' object does not support item assignment
# You can create a tuple from a list via tuple(), and vice versa via list()
t2 = tuple(l)
t2
('hello', '2', 10, [1], 5)
As lists are mutable, we can add new elements, or remove elements from the list.
In particular, []
by itself represents an empty list.
len([])
0
l
['hello', '2', 10, [1], 5]
# Remove via the del keyword
del l[0]
l
['2', 10, [1], 5]
# Remove via accessing and setting to empty list
l[0:2] = []
l
[[1], 5]
l = [1,2 ,3]
Adding elements via list.append()
will add an element to the end of the list:
l.append(5)
l
[1, 2, 3, 5]
Lists allow concatenation (like strings), so you can combine lists or add new elements like the following:
[1, 2, 3] + [4, 5, 6]
[1, 2, 3, 4, 5, 6]
l = [9, 8, 0]
l += [4]
l
[9, 8, 0, 4]
# Insertion at a particular index
l.insert(3, 'hello')
l
[9, 8, 0, 'hello', 4]
Lists allow repetition, like strings:
l * 2
[9, 8, 0, 'hello', 4, 9, 8, 0, 'hello', 4]
And finally, one can check for membership in lists using the in
/ not in
syntax:
l = [1, 'c', 10, False, 'Hello']
1 in l
True
2 in l
False
False in l
True
10 not in l
False
Dictionaries are efficient containers which hold key-value pairs, where each key accesses a specific value in the dictionary. They are defined using curly braces {}
.
# An empty dict
d = {}
type(d)
dict
Adding a key-value pair to a dict is done by the following syntax:
d['key'] = 'value'
d
{'key': 'value'}
d['key']
'value'
d
{'key': 'value'}
d[10] = '13'
d
{'key': 'value', 10: '13'}
# Adding a pair with an integer key
d[10] = True
d
{'key': 'value', 10: True}
We can check if a value exists for a key by the following syntax:
d['key'] # Will return 'value'
'value'
If the provided key does not exist, Python will complain with a KeyError
:
d['hello']
--------------------------------------------------------------------------- KeyError Traceback (most recent call last) <ipython-input-108-b0dc4f55129d> in <module> ----> 1 d['hello'] KeyError: 'hello'
# We can use the get() method for accesing a key-value pair as well:
d.get('key')
'value'
# However, Python will not throw a KeyError if that key does not exist
d.get('hello')
Here's an example of a dictionary with different keys and values:
person = {
'name': 'Cagri Eser',
'email': 'cagrie@metu.edu.tr',
'age': 26,
'phone': 123
}
person['name']
'Cagri Eser'
We can change the value of a key using the same syntax:
person['phone'] = 456
person['phone']
456
And we can delete a key-value pair by specifying the key:
del person['age']
person
{'name': 'Cagri Eser', 'email': 'cagrie@metu.edu.tr', 'phone': 456}
We can access the individual keys and values with:
person.keys()
dict_keys(['name', 'email', 'phone'])
person.values()
dict_values(['Cagri Eser', 'cagrie@metu.edu.tr', 456])
# We haven't seen for loops, but here's a sneak peek:
for key, value in person.items():
print(key, value)
name Cagri Eser email cagrie@metu.edu.tr age 26 phone 456
# Checking for a key using the 'in' syntax
'name' in person
True
Sets are similar to dicts, and they also use curly brackets {}
. Sets are mutable, however, elements of sets must be immutable. Each element in a set can appear only once.
set([1, 2, 3, 3, 4])[0]
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-53-c94472ab8f04> in <module> ----> 1 set([1, 2, 3, 3, 4])[0] TypeError: 'set' object is not subscriptable
s = {1, 2, 3}
type(s)
set
# Sets do not support indexing
s[0]
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-126-cff127f67133> in <module> 1 # Sets do not support indexing ----> 2 s[0] TypeError: 'set' object is not subscriptable
Sets support addition and removal of new elements.
s.add(4)
s
{1, 2, 3, 4}
s.add(4)
s
{1, 2, 3, 4}
s.add('hello')
s
{1, 2, 3, 4, 'hello'}
# Lists can't be added to sets as they are mutable
s.add([1, 2])
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-131-7cfcb40d8c12> in <module> 1 # Lists can't be added to sets as they are mutable ----> 2 s.add([1, 2]) TypeError: unhashable type: 'list'
# However, tuples can be added
s.add((1, 2))
s
{(1, 2), 1, 2, 3, 4, 'hello'}
For immutable sets, you can use frozenset
.
s2 = frozenset(s)
print(type(s2))
s2.add(5)
<class 'frozenset'>
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-133-227a115645f3> in <module> 1 s2 = frozenset(s) 2 print(type(s2)) ----> 3 s2.add(5) AttributeError: 'frozenset' object has no attribute 'add'
Sets have useful functions such as union
, intersection
and other set operations.
s
{(1, 2), 1, 2, 3, 4, 'hello'}
s.intersection({1, 3, 4})
{1, 3, 4}
{1, 2}.union({3, 4})
{1, 2, 3, 4}
Expressions represent calculations from operations among data. These can be arithmetical operations, logical operations, indexing and membership. In terms of priority (precedence), they are ordered in the following manner:
indexing ([]
) -> exponentiation (**
) -> multiplication / division based operators (*
, /
, //
, %
) -> addition / subtraction (+
, -
) -> logical comparison (>
, <=
, etc.) and membership (in
, not in
) -> other logical operators (not
> and
> or
)
Of course, to avoid confusion, don't forget to use parenthesis wherever necessary.
2 + 5 * 7
37
(2 + 5) * 7
49
2 ** 3 ** 5
14134776518227074636666380005943348126619871175004951664972849610340958208
(2 ** 3) ** 5
32768
2 ** (3 ** 5)
14134776518227074636666380005943348126619871175004951664972849610340958208
When dealing with logical and
and or
, Python will take shortcuts instead of evaluating everything directly.
Consider the following examples:
# Python will throw an error when you divide an int or float by zero.
5.2 / 0
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) <ipython-input-56-b58879987c2e> in <module> 1 # Python will throw an error when you divide an int or float by zero. ----> 2 5.2 / 0 ZeroDivisionError: float division by zero
# Normally, you would expect an error to be thrown here as well:
True or 5 / 0
True
In the example above, since True or X
will have the boolean value of True
for any Boolean value of X
, the result will be True
without evaluating the value of X
. Same for the example below:
False and 5 / 0
False
When the result is not clear from the first operand, then the second operand will also be evaluated:
True and 5/ 0
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) <ipython-input-146-5085c5f6b962> in <module> ----> 1 True and 5/ 0 ZeroDivisionError: division by zero
However, Python evaluates such expressions from left-to-right. The following expression, even if it would be trivially True
given that X or True
is always True
, will evaluate the first operand and then throw an error:
# Always True?
(5/0) or True
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) <ipython-input-147-e87242ca2faf> in <module> 1 # Always True? ----> 2 (5/0) or True ZeroDivisionError: division by zero
When applying a binary operator, Python will attempt to implicitly convert one data type to another if they are different types.
a = 5
type(a)
int
b = 5.2
type(b)
float
print(a+b)
type(a+b)
10.2
float
c = 5 +4j
type(c)
complex
print(c+a)
type(c+a)
(10+4j)
complex
In the case of boolean values:
When implicitly converting a bool to a number, True
will be converted to 1 and False
will be converted to 0.
When implicitly converting a number to bool, 0 will be converted to False
, and any non-zero number will be converted to 1.
True + 4
5
True + False
1
bool(5)
True
bool(0)
False
Python supports multiple assignment, and you can also swap values of variables in place without having to use a third temporary variable.
a, b, c = 10, 20, 30
a, b, c
(10, 20, 30)
# This is also possible
l = [8, 9]
a, b = l
a, b
(8, 9)
# Swapping values
a,b = b,a
a, b
(9, 8)
# Using a third variable:
print(a, b)
tmp = a
a = b
b = tmp
print(a, b)
9 8 8 9
5 = a + 2
File "<ipython-input-149-82b8c814bffe>", line 1 5 = a + 2 ^ SyntaxError: cannot assign to literal
a = 5 - 2
a = 5
id(5)
4343241184
id(a)
4343241120
a = 10
id(a)
4343241344
a = [1, 2]
b = a
a
[1, 2]
b
[1, 2]
id(a)
140242499336000
id(b)
140242499336000
b[0] = 5
a
[5, 2]
a = [3, 4]
id(a)
140258063142592
b
[1, 2]
id(b)
140258063071488
a = b
a
[1, 2]
b
[1, 2]
a[0] = 5
a
[5, 2]
b
[5, 2]
b[0] = 5
import copy
a = [1, 2]
b = copy.copy(a)
a[0] = 5
b
[1, 2]
id(a)
140258871003328
id(b)
140259140698432
b = copy.deepcopy(a)
input('whats your name?')
whats your name?hi
'hi'
print('hello')
hello
a = 10
print('Your number is:', a)
Your number is: 10
print(a, a, a, a)
10 10 10 10
print(a, a, a, a, sep='\n')
10 10 10 10
a = 10.2354
print('Your number is {} right'.format(a))
Your number is 10.2354 right
print('Your number is {:.2}, right'.format(a))
Your number is 1e+01, right
b = 20
print('Your numbers are {}, right'.format(a, b))
Your numbers are 10.2354, right
print(f'Number is {a:.2}')
Number is 1e+01
print("Your number was %E, right?" % a)
Your number was 1.023540E+01, right?
def f():
pass
pass
import random
random.random()
0.7730895000759098
random.randint(1, 6)
1
import random as r
r.random()
0.2990374239067727
from random import randint
randint(1, 100)
35
from random import *
pow(2, 5)
32
import time
time.time()
1710504476.6274629