Python Variables
Data types
We have established 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 important 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.
Example:
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)
Type Inference
In Python, data types for variables are optional. When a type is not specified using a type hint, Python automatically determines 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 receives a value and data type, while in other languages we declare a variable with a type, then the assignment is not possible if the type mismatches.
Example:
# 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.
Boolean Values
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.
Example:
In the next example we create two variables a, b of type Boolean.
>>> a = True >>> b = False
Boolean Expressions
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 does not evaluate to 0, "", False, an empty collection, or None is considered truthy. This flexibility is useful, but it can also lead to errors when code relies on implicit truth-value testing instead of explicit conditions.
Examples: of relation operators:
# Is equal:
(1 == 1)
# Is not equal:
(1 != 2)
#Greater than:
(2 > 1)
# Less than:
(1 < 2)
# Greater than or equal to:
(2 >= 2)
# Less than 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 checks object identity, not value equality. It asks whether two names refer to the same object in memory. In everyday code, use == for value comparison and reserve is for identity checks such as value is None.
Value: None
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.
Data Collections
Python collections are implemented using a comprehensive mathematical notation. This makes Python intuitive. Python defines the following collection types:
- list,
- tuple,
- set,
- dictionary
Example:
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.
List collection
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 mixed: 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 uses 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]
Notes:
- elements in a list are ordered in the order of insertion,
- element index in list start from [0] and last element is (-1),
- negative index x represents count-x where count is the total number of elements,
- a list do not have gaps and always starts from index = 0 even if we remove elements.
Tuple collection
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 cannot add a new element and you cannot remove elements from a tuple. However you can check if one element belongs to a tuple.
Example:
# tuple with a single element
a = (1) # this is just an integer expression
a == 1 # True
a = (1,) # correct single-member tuple
a == (1,) # True
# 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
Set collection
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 unordered. You cannot access a set using index notation. If my_set = {1,2,3} then my_set[0] will not work.
Example:
# 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}
Dictionary
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.
Example:
# 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"]
Deconstruction
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:
Examples:
#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.
Practice: Copy the deconstruction example into a local script and change the number of captured values to see how unpacking behaves.
Spreading
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.
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
Practice: Run the spreading example locally and compare the output of test(args) versus test(*args).
Comprehension
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.
Example:
# 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]
Practice: Run the comprehension example locally, then add one more filter and one more dictionary transformation.