We have establish that a variable represents data. But what exactly is the data? In low level computation data is stored in memory as bits: "0" or "1" grouped in bytes (8 bits). This is the actual binary data. The way data is organized in memory is not of importance for a Python developer. Python can manipulate abstract data types: {numeric, string, boolean, timestamp} and this is what is important to study in order to comprehend programming.
Python is a dynamic typed language. But what this means exactly? Let's explain it by using an example:
#declare a variable with type
x: int = 5
print(var)
#change data value and type to a string
x: str = "test"
print(x)
In Python, data types for variables is optional. When is not specified using a type hint, Python automatically determine the data type from data literals or expressions used to set the variable value. In Python an identifier becomes a variable if a value is assigned to it. The assignment is done using symbol "=" (equal). When a variable is assigned it receive value and data type, while in other languages we declare variable with type then the assignment is not possible if the type mismatch.
# type inference demo
v = 100 #declare an integer variable
r = 100.25 #declare a float variable
# reassign variable v
v = 22.01 #the type of variable v is now changed to float
# Strings can be enclosed with double quotes ("...")
# or single quotes ('...'). Best use double quotes.
s1 = "this is a string"
s2 = 'this is also a string'
Note: This syntax do not use a type hint, it uses type inference instead.
Logical values in Python are True = 1 and False ==0. These are also known as Boolean values and are used in Boolean algebra.These values are constants and can be used in conditional expressions to make decisions.
In the next example we create two variables a, b of type Boolean.
>>> a = True >>> b = False
Boolean expressions are expressions that return a Boolean value. Boolean expressions are also known as conditional expressions. The most common Boolean expressions are created using relation operators. You can compare numbers, strings or object references using relation operators.
In Python an expression that do not return 0 or "", Null or None it is considered True. So any number > 0 is also True and any number = 0 is actually False. In our opinion this can lead to errors. A better programming language will be more restrictive.
Examples: of relation operators:
# Is equal:
(1 == 1)
# Is not equal:
(1 != 2)
#Greater then:
(2 > 1)
# Less then:
(1 < 2)
# Greater then or equal to:
(2 >= 2)
#Less the or equal to:
(0 <= 1)
#Is the same object:
# given a = b = 1;
(a is b)
# Member of collection:
# given a = 1;
(a in [1,2,3])
Note:Operator "is" demonstrate that a is actually same as b. If we change ones value, the other will have the same new value. So a, b are pointing to same memory location. In mathematics we use symbol "≡" for this operation: identical.
There is a peculiar value in Python: "None" this value is not True and not False however the logical operators work with this value as if is False. Also "if" statement evaluate None as False.
Note: A variable that has value None does exist, you do not get an error if you use it in expressions. Usually an expression that use a None value will have as result None. This is not printable value though.
Python collections are implemented using a comprehensive mathematical notation. This makes python intuitive. Python define following collection types:
In next example we create 4 collections.
# Define a list of elements
my_list = [1,2,3,4,5]
# Define a tuple of elements
my_tuple = (1,2,3,4,5)
# Define a set of elements
my_set = {1,2,3,4,5}
# Define a dictionary
my_dic = {"a":1,"b":2,"c":3}
Note: Each collection is initialized using a special notation explained next.
A list is a dynamic collection of elements ordered in the order of creation. You can add elements and remove elements from a list. The elements can have any data type and can be mix: strings with numbers. The element is indexed from 0 to number of elements -1. So you can use a subscript to refer to a particular element from the list.A list is using notation: [1,2,3].
An empty list is like this: [].
# define a list with 4 elements
my_list = [1,2,3,4,5]
# following expressions are true:
print(my_list[0] == 1) # True
print(my_list[4] == 5) # True
print(my_list[-1] == 5) # True
# add new element to the list
my_list.append(6)
print(my_list) # [1,2,3,4,5,6]
# remove element from list
my_list.remove(5)
print(my_list) # [1,2,3,4,6]
A tuple is like a list except it is not dynamic. Once you set a tuple the number of elements and the value of elements can't be modified. That is a tuple is an immutable structure. You can only read element value from a tuple using a subscript. A tuple is using notation: (1,2,3). Empty tuple is the unit: ().
Tuple elements can be accessed by index like a list, in order. But you can not add a new element and you can not remove elements from tuple. However you can check if one element belong to a tuple.
# tuple with a single element
a = (1) # this is actually an expression
a == 1 # wrong not a tuple
a = (1,) # correct single member tuple
a ==(1,) # expected type is tuple
# using operators "in" and "not in"
a = (1,2,3) # tuple of 3 elements
1 in a # true
4 in a # false
4 not in a # true
A set is like a list except that all elements are unique.You can't have duplicates. The elements can be any kind of data. Elements of a set can't be addressed by a subscript. You can test if a value is in the set using the operator "in" or "not in". A set is using literal notation: {1,2,3}. Empty set is {}.
Note: Elements in a set are sorted, not ordered in the order of insertion. You can not access a set using index notation. If my_set = {1,2,3} then my_set[0] will not work.
# create a set with initial value
a = {1,2,3}
# check if element is in set
print(3 in a) # True
# append new elements
a.add(4) # new element
a.add(3) # existing element
print(a) # {1,2,3,4}
b = a | {4,5,6} # union
c = a & {1,2,7,8} # intersect
print(b) # {1,2,3,4,5,6}
print(c) # {1,2}
# remove elements
a.discard(4);
print(a) # {1,2,3}
A dictionary is a hash table. It is a pair of key and value like an index of a book. The key is unique and the value can be any kind of data type and can have duplicates. To define a dictionary we can use literal notation: {key:value, key:value}
The key can be integer, string or tuple. Python is sorting a dictionary by the key. To get a value from dictionary you must have the key. You can use the key like a subscript index to get or set the value.
# define a dictionary
dic = {"a":1, "b":2}
# following expressions are true
if dic["a"] == 1: print("True") # True
if dic["b"] == 2: print("True") # True
# you can add elements using key assign
dic["c"] = 4
print(dic) # {"a":1, "b":2, "c":4}
# you can add elements using update()
dic.update(d = 5) # (key becomes identifier)
dic.update(e = 6, f = 7)
dic.update({"g":8,"h":9})
# you can remove element using pop() or del
dic.pop("b")
del dic["g"]
One interesting feature related to collections is the ability to extract values from a collection using assign operator into multiple variables. If you combine this feature with operator: "*" you can shorten your assign expressions considerable:
#deconstructing a tuple
a, b = (1,2)
#ignoring one member
a,_,c = (1,2,3)
print(a,c) # 1 3
#deconstructing a list
x,y,*z = [1,2,3,4,5]
print(z) # [3,4,5]
#ignore all except first and last
f,*_,w = {1,2,3,4,5}
print (f, w) # 1 5
Note: Operator * is called in this case "rest" operator. It is very similar to "..." used in JavaScript. Combination "*_" is called "ignore some" and I have never seen this before in any other language.
You can test this example here: deconstruction
When you have a function that is expecting multiple arguments, you can use operator "*" as prefix to a collection to spread its members into individual elements. In other languages, equivalent to this operator is "..." like JavaScript for example.
# function with variable arguments
def test(*params):
print(params)
# spreading a list into arguments
args = [1,2,3]
test(args) # unexpected: ([1,2,3],)
test(*args) # expected: (1,2,3)
# use spreading to form new list
new = [0,*args,4,5]
print(new) # [0,1,2,3,4,5]
print(*new,sep = ";") # 0;1;2;3;4;5
Test this example live: spreading
There is a cool notation available in Python to manipulate parts of collections. This is very similar to "set builder" notation that we learn in Mathematics.Once you understand this notation you can do wonderful miracles in a single line.Python is such a productive language due to this notation.
# data source (fixture)
source = [1,2,2,3,3,4,4,5,5]
# classic notation
unique = set([1,2,2,3,3,4,4,5,5]);
print("unique=",unique) # {1, 2, 3, 4, 5}
# list comprehension
build = [ x**2 for x in unique]
print("build=",build) # [1,4,9,16,25]
# Filtering elements
even = [ x for x in unique if (x % 2 == 0) ]
print("even=",even) # [2,4]
# set comprehension
newset = { x for x in source }
print("newset=",newset)
# dictionary comprehension with mapping
newdic = { x : x**2 for x in unique }
print("newdic=",newdic) # {1:1,2:4,3:9,4:16,5:25]
Check this example live: comprehension
Read next: Control Flow