In the previous tutorial, we learned about Python’s syntax — meaning its identifiers, keywords, comments, statements, literals, and data types. We also learned that Python is object-oriented and that these objects can be mutable or immutable. Python objects that point to a group of values are iterable, which, in this case, is referred to as an iterator.
All of the data types covered in the previous tutorial (integers, floats, complex numbers, strings, raw strings) are data types for literals (real values assigned to variables or attributes). Python also has data types for references that point to a group of ordered or unordered values (items).
In addition, Python has the following data types:
Tuples – a sequence of objects. A sequence refers to an ordered group of items (objects), where the indices can access the items. The items of a tuple can be an arbitrary object that’s mutable or immutable.
However, the tuple is immutable, which means the items of a tuple cannot be updated or changed once defined. This is why it’s rarely recommended to use mutable items in a tuple unless they will never change or require updating. A tuple can be seen as a group of constant values or objects and can be used to define one or more constants.
A tuple is defined as a group of values or objects separated by spaces or commas. The group can be optionally enclosed by parenthesis (“(“). The commas are also optional, but it’s recommended to use commas to separate tuple items, particularly if the tuple is necessary for a function.
Optionally, the last item of the tuple can also have a trailing comma. An empty tuple is defined by an empty pair of parentheses. The built-in function, tuple(), can also be used to define a tuple. Any group of items separated by spaces or commas will default to a tuple. If a single string (or any iterable) is defined as a tuple, its characters (or items) can be used as tuple items.
Here are a few valid examples of tuples:
tup1 = ‘Id’ ‘Name’ ‘Address’
tup1 = ‘Id’, ‘Name’, ‘Address’
tup1 = (‘Id’, ‘Name’, ‘Address’)
tup1 = (‘Id’, ‘Name’, ‘Address’,)
tup1 = () #Empty Tuple
tup1 = (‘Id’, 123, 0b1111_1110, 2.5j5.4, True)
tup1 = (‘Id’, 123, 0b1111_1110, 2.5j5.4, True,)
tup1 = tuple(‘Id’, ‘Name’, ‘Address’)
tup1 = tuple(‘Id’ ‘Name’ ‘Address’)
tup1 = tuple(‘ABC’) # is equivalent to tup = (‘A’, ‘B’, ‘C’)
Lists – sequences ordered as a group of mutable items. This means the items in a list can be changed or modified. Essentially, lists are a modifiable group of values or objects where the items of the group can be replaced, removed, or added. The items of a list can include any arbitrary object that’s mutable or immutable.
A list is defined as a group of values and/or objects separated by commas within brackets (“[“). Optionally, the last item of the list can have a trailing comma. The built-in function, list, can also be used to define a list.
An empty list is defined by an empty pair of brackets or the list() function without arguments. If an iterable is used as a sole item for a list, its items can be used as the items of the list.
Here are a few valid examples of lists:
list1 = [‘Id’, ‘Name’, ‘Address’]
list1 = [‘Id’, ‘Name’, ‘Address’,]
list1 = [] #Empty List
list1 = list() #Empty List
list1 = [‘Id’, 123, 0b1111_1110, 2.5j5.4, True]
list1 = [‘Id’, 123, 0b1111_1110, 2.5j5.4, True,]
list1 = list(‘Id’, ‘Name’, ‘Address’)
list1 = list(‘Id’ ‘Name’ ‘Address’)
list1 = list(‘ABC’) # is equivalent to list1 = [‘A’, ‘B’, ‘C’]
Sets – a mutable, unordered collection of unique items. The items of a set should be immutable and “hash-able,” which means that its value (the hash value) should never change. Immutable means that its data type must also never change. This is because a set can only contain immutable and hash-able items without any other set within it.
A set is defined by a group of values and/or (immutable and hashable) objects separated by commas within braces (“{“). Optionally, the last item of the set can have a trailing comma. The built-in function, set, can also be used to define the set. An empty set is defined by an empty pair of braces or set function without arguments.
Here are valid examples of sets:
set1 = {‘Id’, ‘Name’, ‘Address’}
set1 = {‘Id’, ‘Name’, ‘Address’,}
set1 = {} #Empty Set
set1 = set() #Empty Set
set1 = {‘Id’, 123, 0b1111_1110, 2.5j5.4, True}
set1 = {‘Id’, 123, 0b1111_1110, 2.5j5.4, True,}
set1 = set(‘Id’, ‘Name’, ‘Address’)
set1 = set(‘Id’ ‘Name’ ‘Address’)
Frozensets – immutable version of sets. They are defined by the frozenset() function. An empty frozenset is defined by the frozenset function without arguments.
Here are valid examples:
frset1 = frozenset(‘Id’, ‘Name’, ‘Address’)
frset1 = frozenset(‘Id’ ‘Name’ ‘Address’)
frset1 = frozenset() #Empty Frozen Set
Dictionary – a mutable, unordered collection of values paired with arbitrary keys. These keys are arbitrary but they should be hash-able. The values can include any arbitrary mutable or immutable objects.
The dictionary is similar to an unordered map or a hash table in other programming languages. It’s defined as a group of commas, separated pairs of keys, and values enclosed within braces or used as arguments in the built-in dict() function.
The in key-value pair, key, and value are separated by a colon (“:”). In the dict() function, key, and value in a pair can be separated by an equal sign (“=”) or can be placed in a tuple. Optionally, the last item (key-value pair) of a dictionary can have a trailing comma.
An empty dictionary is defined by an empty pair of braces or the dict() function without arguments. Any item in a dictionary should be a key-value pair even if the value is iterable.
Here are valid examples of dictionaries:
dict1 = {x:25, y:57, z:64}
dict1 = {x:25, y:57, z:64,}
dict1 = {‘x’:25, ‘y’:57, ‘z’:64,}
dict1 = {‘x’:25, 57:’y’, z:8.9j6.5}
dict1 = dict(x=25, y=57, z=64)
dict1 = dict((x, 25), (y, 57), (z, 64))
dict1 = dict() # Empty Dictionary
dict1 = {} # Empty Dictionary
None – a null object in Python, which is equivalent to “no object” and has zero attributes or methods. A function returns None if it does not have a return statement.
Byte – immutable sequences (ordered collection), where only items can be bytes (integers in the range of 0 to 255). The items of a Byte can be characters of a string, integers, None, or iterables.
Bytes can be defined by pre-fixing “b” before a string or integer or using a built-in byte() function. The Byte() function also allows for specifying an encoding.
Here are a few valid examples:
Val = b’Python’
Val = b’123’
Val = bytes(‘Python’)
Val = bytes(‘Python’, utd-8)
Val = bytes ([1, 2, 3, 4, 5])
Byte arrays – mutable sequences (ordered collection), with only items (integers in the range of 0 to 255). The items of a byte array can be characters of a string, integers, None, or iterables.
The only difference between Byte and Byte array types is that a byte is immutable, whereas the byte array is mutable. A byte array can be defined using the built-in bytearray() function.
Here are a few valid examples:
Val = bytearray(“Python”, ‘utf-8’)
Val = bytearray([1, 2, 3, 4, 5,])
Val = bytearray() # Empty bytearray
Range – a sequence (ordered collection) of integers between two integer values such that each integer value (item) differs by an adjacent item via a step value. By default, adjacent values are separated by a difference of “1.”
A range is refined using the built-in range() function. The range() function has the following syntax:
range(start, stop, step)
- If only one value is provided as an argument in the range(), it provides a sequence of integers from 0 to the given argument.
- If two values are provided as an arguments, it gives a sequence of integers between the two values, which include them.
- If three values are provided as an arguments, the third value serves as the difference between each adjacent item.
Here are valid examples of a range:
range(9) # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
range(5, 10) # 5, 6, 7, 8, 9, 10
range(2, 10, 2) #2, 4, 6, 8, 10
Boolean – this type can have only two values : True or False. Any non-zero value or non-empty container is considered True. Any zero value, None, or Empty container, is considered False. The True and False values can also be denoted by “1” and “0,” respectively.
There are several other data types in Python that can have built-in classes (such as datetime) or user-defined classes. For example, any class that defines a type of data by purpose or denotes a data structure can be considered a data type class.
There are six types of sequences (ordered collections) in Python:
- Strings
- Bytes
- Byte arrays
- Range
- Tuples
- Lists
The indices of sequences must start from “0” (i.e. the first item of any sequence has an index/key of “0”). sets, frozen sets, and dictionaries are unordered collections in Python.
For clarity, refer to this table:
Name of Collection | Nature of Collection | Nature of Items |
String | Immutable sequence (ordered collection) of ASCII or UTF (or other encoding) characters | ASCII or UTF characters or characters corresponding to applied encoding scheme |
Byte | Immutable sequence of bytes (0 to 255) | Byte values in range from 0 to 255 that can be integers, characters or iterables |
Byte Array | Mutable sequence of bytes | Byte values in range from 0 to 255 that can be integers, characters or iterables |
Range | Immutable sequence of integers | Integers between defined range |
Tuple | Immutable sequence of objects | Arbitrary mutable or immutable objects |
List | Mutable sequence of objects | Arbitrary mutable or immutable objects |
Set | Mutable unordered collection of objects | Arbitrary immutable objects, duplicate objects not allowed |
Frozen Set | Immutable unordered collection of objects | Arbitrary immutable objects, duplicate objects not allowed |
Dictionary | Mutable unordered collection of key-value pairs | Arbitrary immutable keys and arbitrary mutable or immutable values |
Variables, attributes, and items
In Python — where everything is an object — data values are also objects. The data values are stored in memory. This means whenever a new data value appears in the Python code (whether defined in code or at runtime), it’s also stored in some memory location.
The code accesses these data values through references. These references can be variables, attributes, methods, or items of an object. The variables are references to data values that can be used anywhere in the code.
The attributes are references to non-callable data values associated with an object. Methods are references to “callable” data values associated with an object. Being callable means these data values execute a group of statements at their occurrence. In Python, instances of a class are called items. So, items are references to instances of a class or objects.
Whenever a data value is assigned to a variable, attribute, method, or object, the process is called binding. The data value remains independent of its binding. If a different data value is assigned to a reference, that data value is stored in a different memory location. As a result, the previous data value (if not bound to any other reference) becomes unbound. The cleanup of unbound data values or objects is called garbage collection.
There are no declarations in Python. The references appear on the fly and have no declared data type. How a reference (to a data value/object) is treated depends on the expression in which it appears and how/if it’s used to rebind another reference.
The data values can be bound to a reference through an assignment or augmented assignment statements. In an assignment statement, an RHS expression is bound to an LHS target and they are separated by assignment sign (“=”). The target can be any reference, such as a variable, an attribute, or an item.
A simple assignment statement looks like this:
var = Expression
This is an example of simple assignment:
a = 1
It’s possible to assign a data value (or the result of an expression) to multiple targets at the same time:
var1 = var2 = var3 = Expression
This is an example of such an assignment:
a = b = c = 1
If the RHS expression is an iterable or results in an iterable, it should be assigned to another iterable (of the same size) or multiple variables (equal in number to the size of the iterable) as follows:
var1, var2, var3 = Iterable/Expression resulting into Iterable
Here’s an example of such a statement:
a, b, c, = [1.1, 1.9, abc]
If a target reference has a star sign (“*”) before it, it accepts a list of all data values/objects that are left unassigned as follows:
var1, *var2, var3 = Iterable/Expression resulting into Iterable
Here’s an example of such an assignment:
a, *b, c, = 1.1, 1.9, abc, 43, 2.5j4 # b = 1.9, abc, 43
In an augmented assignment, an augmented operator, such as +=, -=, *=, **=, is used for an assignment. The target reference should be created before use in such a statement. There can be only one target reference in an augmented assignment.
In the augmented assignment, the previously assigned data value to a reference is modified and rebound to it. The previously assigned data value, then, becomes unbound (from that particular reference).
Here’s an example of an augmented assignment:
a += b # a = a + b
The data values can be unbound from references using a del statement. This is called unbinding. It’s possible to unbind multiple references in a single del statement by separating the target references by a comma. However, a del statement only unbinds a data value/object to a reference and does not delete the data value or object. If the data value is unbound to any other reference, it’s left for garbage collection.
Here are valid examples of del statements:
del a
del a, b, c
Python operators
The operators are used for manipulating data values and with references forming the logical expressions. There can be one or more expressions within a logical statement. Python supports many operators.
These are the most common:
Arithmetic operators – include addition (+), subtraction (-), multiplication (*), division (/), modulus (%), exponentiation (**), and floor division (//). The addition operator (*) also serves as a concatenation operator with strings and other iterables. Similarly, the multiplication operator (*) can be used to copy a string or iterable by the times specified using a multiplier.
Assignment operators – include assign (=), add and assign (“+=”), subtract and assign (-=), multiply and assign (*=), divide and assign (/=), modulus and assign (%=), exponentiation and assign (**=), and floor division and assign (//==).
Comparison operators – include equal (==), not equal (!= or <>), greater than (>), less than (<), greater than or equal to (>=), and less than or equal to (<=).
It’s also possible to chain comparisons as follows:
a < b >= c # a < b and b >=c
Logical operators – include logical AND (and), logical OR (or), and logical NOT (not).
Bitwise operators – include binary AND (&), binary OR (|), binary XOR (^), binary ones compliment (~), binary left shift (<<), and binary right shift (>>).
Membership operators – include in (in) and not in (not in). These operators are used to evaluate if an item belongs to a sequence or collection. The in (in) operator returns true if the LHS item is a member of the RHS sequence/collection. The not in (not in) operator returns true if LHS item is not a member of RHS sequence/collection.
Identity operators – include is (is) and is not (is not). These operators are used to evaluate if two references are identical. The two references can have the same identity if they point to the same data value, which may be stored at the same memory location (depending on the operating system and platform). The is (is) operator returns true if both references have the same identity. The is not (is not) operator returns true if both references don’t have the same identity.
Indexing operator – the indexing operator ([]) is used with sequences to access a specific item of them. The indices in all of the sequences begin with “0,” so that the first item of any sequence has an index (or key) of 0. The indices of subsequent items increase by “1.” So, x[2] lets you access the third item of a sequence “x.” If a negative number is used as an index, that item will be accessed counting from the last item of the sequence instead of from the first one.
Slicing operator – the slicing operator ([:]) is used to access multiple items of a sequence as a list. For example, x[1:3] lets you access the second and third items of a sequence “x” as a list. Remember, Python has six types of sequences: strings, bytes, byte arrays, range, tuple, and list.
Operator precedence in Python
The following table summarizes the operator precedence in Python from the lowest precedence to the highest precedence:
Control flow statements
The Python supports the following control flow statements:
If-elif-else – a conditional statement that allows executing statement(s) if the conditional expression(s) return true. It has this syntax:
if conditional_expression1:
statement(s)
elif conditional_expression2:
statement(s)
elif conditional_expression3:
statement(s)
else conditional_expression:
statement(s)
In the if statement, the elif and else are optional. As a conditional expression evaluates to True, the statements preceding it are executed and the execution of the if statement ends there. However, a conditional expression doesn’t have to return as True or False, explicitly. In Python, any expression or object implicitly evaluates True or False as a conditional expression.
So, it is recommended to use the following expression:
If x:
If not x:
Rather than the following expressions:
If x == True:
If x == 1:
If x is True:
If bool(x):
If x == False:
If x == 0:
If x is False:
It’s important to note that Python does not support a switch statement.
While statement – repeats a statement or block of statements if a conditional expression evaluates to True. It continues repeating until the conditional expression evaluates to False. This means that a statement should contain code that eventually makes the conditional expression False.
The while statement has this syntax:
While conditional_expression:
Statement(s)
The statements that are repeatedly executed in a while statement, are called a loop body. If the while is used inside a function, and the loop body contains a return statement, the loop ends there — as the return statement ends the execution of the function. Similarly, the break and continue statements can also affect the while statement.
For-in statement – allows executing a statement or block of statements while iterating through items of an iterable. A target reference accesses the items of the iterable. The items of the iterable may be used by the statement(s) or not. But the iterable should never be modified in a for-in statement like the while iterating through a list, set, or dictionary, The items should not be added or removed from them.
The for-in statement has this syntax:
for target_reference in iterable:
Statement(s)
There can be more than one target reference separated by commas if the iterable is a dictionary or other data structure where each item contains more than one data value. For example, the following for statement iterates through a dictionary:
for key, value in d.items():
print(key + ‘ : ’ + value + ‘/n’)
If a single statement has to be executed on each item of an iterable to obtain a new iterable, the for statement can be combined with an assignment statement as follows:
y = x+3 for x in range(5)
In such a statement execution of the expression can also be conditional, like this:
y = x+3 for x in range(5) if x%2==0
Similar statements are possible with lists, sets, and dictionaries.
Break statement – used in a loop body to terminate the loop. It’s often used with an if statement. As the break statement is found, the loop ends.
Here are some valid examples of a break statement:
While x: #execute loop if x is not zero or none
If x<10 :
print(x)
x += 1
else:
break
While x: #execute loop if x is not zero or none
If x>10:
break
else:
print(x)
x +=1
Continue statement – used in a loop body to leave the execution of statements after it for only the current iteration of the loop. It’s often used with the if statement.
Here’s a valid example:
while x<=10:
if x%2 == 0: continue
print(x)
Else statement – used with an if, for, and while statements. When used with the for or while, it executes a statement or a block of statement after the natural termination of the loop (i.e. after all items of an iterable in the for statement or when a conditional expression of a while statement evaluates as False).
Pass statement – used in the if statement if nothing should be done for a conditional expression. It has this syntax:
if conditional_expression:
Statement(s)
elif conditional_expression:
Statement(s)
elif conditional_expression:
Pass #Nothing to do if this condition meets
else:
Statement(s)
Exception handling – Python supports try, except, finally, else, and with statements for exception handling. An exception can be raised in the code explicitly by a raise statement. When an exception is raised implicitly or explicitly, the normal execution of code deviates until that exception is settled.
Functions
Functions are callable blocks of statements. In Python, functions are also objects. They can be:
- Assigned to references (variables, attributes, and other objects)
- Included as an item in an iterable
- Used as an argument in other functions
A function can also return another function. It’s defined using a def statement and has this syntax:
def function_name(parameters):
statement(s)
The function-name is an identifier that can be used to make function calls. The parameters are references that must be supplied to a function when it’s called. The parameters (or arguments) are optional and if a function requires no references to be supplied, there’s no reason to include them in the function definition.
If there are multiple parameters, they should be separated by commas within parenthesis in the function definition. There can be one or more statements that execute on calling the function. These statements are called the body of the function.
It’s possible to have optional parameters in a function that may or may not be supplied when the function is called. These optional parameters are specified by an assignment expression, where the optional parameter is assigned a default value.
A function with optional parameters has this syntax:
def function_name(parameter, optional_parameter = expression):
statement(s)
Also, this a valid example of a function with optional parameters:
def makeList(x, y=[]):
y.append(x)
return y
A function can return any data value or object using a return statement, which can only be used in the body of a function. If the function does not have a return statement, an expression, a data value, or an object after a return keyword, the function returns no object.
The parameters that are not optional are called positional parameters. It’s possible to supply any arbitrary number of positional parameters to a function with a pre-fixing asterisk (*) or double asterisk (**) before a parameter.
When pre-fixing an asterisk (*) to a parameter, it accepts any arbitrary number of positional parameters and collects them in a tuple. When pre-fixing a double asterisk (*) to a parameter, it accepts any arbitrary number of positional parameters and collects them in the dictionary.
Here’s a valid example of accepting an arbitrary number of the positional parameters in a function:
def add(*num)
return sum(num)
s = add(15, 20, 15) # s = 50
The references supplied to a function when called are known as arguments. The arguments can be supplied as positional or named arguments. If the data values/objects are used as arguments without assigning to parameter references, they’re accepted in the function call by the position of their appearance in the function signature. (Note: the function signature is the collection of parameters bind to a function).
These are called positional arguments. If the data values/objects are used as objects by explicitly assigning them to parameter references, the position of their appearance in a function call does not matter. These are called named or keyword arguments.
Here’s a valid example of the positional arguments:
def f(a, b, c):
return a = b= c
f(12, 17, 21) # returns 50
This is a valid example of a named (keyword) arguments:
def f(a, b, c):
return a = b= c
f(b = 12, a = 17, c = 21) # returns 50
In Python, it’s possible to define a function inside another function and this is called. The function defined inside another function is called a nested function and the function in which another function is defined is called the outer function. A nested function can use the parameters and references of an outer function. Such a nested function is called a closure.
A function can also return the values/objects at different times. For example, it can have a yield or yield from statements. Such functions are called generators. When a generator is called, it returns the value/object or result of an expression in the first yield statement and saves all local references in its memory.
When the function is called again, it starts execution from the statements after the first yield statement and returns the value/object or result of expression after another yield statement. The yield from a statement can be used if the expression with it is an iterator.
Here’s a valid example of a generator function:
def f(a, b, c):
yield a
yield a + b
yield a + b + c
for x in f(2, 5, 7): print(x) # returns 2, 7, 14
The generator uses the yield from statements:
def f(a, b, c):
yield from range(a)
yield from range(b)
yield from range(c)
for x in f(1, 2, 3): print(x) # returns 0, 1, 0, 1, 2, 0, 1, 2, 3
Python also supports recursive functions, which are those that call themselves inside their own body.
Here’s a valid example of a recursive function in Python:
def factorial(n):
if n==1: return 1
else return n * factorial(n-1)
Scope of references
In Python, references can have a local or global scope. This is called their namespace. The references that bind to a function or other objects are local to that function or object. These references can be used only within the function or object they are bound to.
The references that appear outside the functions or other objects have a global scope. These references can be used anywhere in the code. A reference can be made global by prefixing a global keyword.
This is a valid example:
a = 1
def f(b):
global b
return a + b
In the next tutorial, we’ll learn more about byte arrays, lists, sets, and dictionaries. Although strings, bytes, range, and tuple are immutable sequences, the byte array and list are only mutable sequences in Python. Sets and collections are most frequently used in unordered collections.
You may also like:
Filed Under: Python, Tutorials