Function

Function arguments refer to data passed to the function. In python, there four main types of arguments: positional arguments, keyword arguments, default arguments, and variable length arguments.

  • Positional arguments are passed to a function in a specific order, and the values are assigned to parameters based on their position.
  • Keyword arguments are passed to a function by explicitly specifying the parameter name. Any keyword arguments must be put after the last positional argument.
  • A default argument allows a function to accept values for parameters that are not provided by the caller. If no argument is passed for a parameter with a default value, the default will be used. Any default arguments must be put after the last positional argument.
  • Variable length arguments allow a function to accept any number of positional arguments. *args accepts all arguments into a tuple, while **kwargs accepts all arguments into a dictionary.
SHOW CODE
def function_arguments(a, b=2, *args, c=3, **kwargs):
    print(f"a: {a}, b: {b}, args: {args}, c: {c}, kwargs: {kwargs}")


function_arguments(1, 1997, 9, 99, 999, name="Signal", age=27)
SHOW OUTPUT
a: 1, b: 1997, args: (9, 99, 999), c: 3, kwargs: {'name': 'Signal', 'age': 27}

In Python, functions always return a value, if no return is specified, they return None.

SHOW CODE
def check_even(num):
    if num % 2 == 0:
        return True
    # No else clause, so returns None if odd


print(check_even(3))  # None

In Python, a variable defined outside a function is accessible within the function’s body, but only for reading its value. If a value is assigned to the variable inside the function, a new local variable with the same name is created, and the external variable remains unchanged.

SHOW CODE
x = 1


def fun():
    print(x)


fun()  # 1
x = 1


def fun():
    x = 2
    print(x)


fun()  # 2

To modify a variable defined outside a function, the global keyword can be used, extending the variable’s scope into the function and allowing both reading and modification.

SHOW CODE
x = 1


def fun():
    global x
    x = 2
    print(x)


print(x)  # 1
fun()  # 2
print(x)  # 2

For function arguments, if the argument is a list, modifying the parameter itself doesn’t affect the original list, but modifying the list’s contents will affect the original list.

SHOW CODE
def fun(list_1):
    print("Before modification...")
    print(f"list_1: {list_1}")
    print(f"list_2: {list_2}")
    list_1 = [0, 0]  # Modify the parameter itself
    print("After modification...")
    print(f"list_1: {list_1}")
    print(f"list_2: {list_2}")


list_2 = [1, 2]
fun(list_2)
print(f"Outside the funtion: list_2 = {list_2}")

######################### Output #########################
Before modification...
list_1: [1, 2]
list_2: [1, 2]
After modification...
list_1: [0, 0]
list_2: [1, 2]
Outside the function: list_2 = [1, 2]
##########################################################

##################### Memory Diagram #####################
+-----------+       +--------+       +-----------+
| list_2    | ----> | [1, 2] | <---- |  list_1   |
+-----------+       +--------+       +-----------+

############### Executing list_1 = [0, 0] ################
+-----------+       +--------+
| list_2    | ----> | [1, 2] |
+-----------+       +--------+

+-----------+       +--------+
| list_1    | ----> | [0, 0] |
+-----------+       +--------+
##########################################################
def fun(list_1):
    print("Before modification...")
    print(f"list_1: {list_1}")
    print(f"list_2: {list_2}")
    list_1[0] = 999  # Modify the list's content
    print("After modification...")
    print(f"list_1: {list_1}")
    print(f"list_2: {list_2}")


list_2 = [1, 2]
fun(list_2)
print(f"Outside the function: list_2 = {list_2}")

######################### Output #########################
Before modification...
list_1: [1, 2]
list_2: [1, 2]
After modification...
list_1: [999, 2]
list_2: [999, 2]
Outside the funtion: list_2 = [999, 2]
##########################################################

##################### Memory Diagram #####################
+-----------+       +--------+       +-----------+
| list_2    | ----> | [1, 2] | <---- |  list_1   |
+-----------+       +--------+       +-----------+

############### Executing list_1[0] = 999 ################
+-----------+       +----------+       +-----------+
| list_2    | ----> | [999, 2] | <---- |  list_1   |
+-----------+       +----------+       +-----------+
##########################################################

Operators

  • Floor division // rounds down the result of the division, truncating any fractional part. For example, -6 // 4 outputs -2.
  • Left-sided binding refers to the way operators or expressions are evaluated from left to right. Most operators evaluate left-to-right. For example, 9 % 6 % 2 returns 1.
  • Right-sided binding refers to expressions where the evaluation happens from right to left. The exponentiation operator (**) and the chained assignment evaluate right-to-left.
  • Compound assignment operators allow performing an operation and then assign the result of it to a variable in a single step. For example, the expression a /= 2 * 3 evaluates 2 * 3 to 6, and then performs a = a / 6 that returns 1.0.

List

When assinging a list to a new variable, both variables reference the same list object in memory, meaning modifying one variable affects the other because they point to the same data.

SHOW CODE
list_1 = ["A", "B", "C"]
list_2 = list_1
list_3 = list_2

###################### Memory Diagram #####################
+-----------+       +-----------------+       +-----------+
| list_1    | ----> | ["A", "B", "C"] | <---- |  list_2   |
+-----------+       +-----------------+       +-----------+
                            ^
                            |
                       +-----------+
                       |  list_3   |
                       +-----------+
###########################################################

del list_1[0]
################### Memory Diagram ###################
+-----------+       +------------+       +-----------+
|  list_1   | ----> | ["B", "C"] | <---- |  list_2   |
+-----------+       +------------+       +-----------+
                          ^
                          |
                    +-----------+
                    |  list_3   |
                    +-----------+
######################################################

del list_2
####### Memory Diagram ###########
+-----------+       +------------+
|  list_1   | ----> | ["B", "C"] |
+-----------+       +------------+
                          ^
                          |
                    +-----------+
                    |  list_3   |
                    +-----------+
##################################

print(list_3)  # ["B", "C"]

To copy the contents of the original list, slicing is commonly used.

SHOW CODE
list_1 = ["A", "B", "C"]
list_2 = list_1[:]
list_3 = list_2[:]
############ Memory Diagram ###########
+-----------+       +-----------------+
| list_1    | ----> | ["A", "B", "C"] |
+-----------+       +-----------------+
+-----------+       +-----------------+
| list_2    | ----> | ["A", "B", "C"] |
+-----------+       +-----------------+
+-----------+       +-----------------+
| list_3    | ----> | ["A", "B", "C"] |
+-----------+       +-----------------+
#######################################

del list_1[0]
############ Memory Diagram ###########
+-----------+       +-----------------+
| list_1    | ----> | ["B", "C"]      |
+-----------+       +-----------------+
+-----------+       +-----------------+
| list_2    | ----> | ["A", "B", "C"] |
+-----------+       +-----------------+
+-----------+       +-----------------+
| list_3    | ----> | ["A", "B", "C"] |
+-----------+       +-----------------+
#######################################

del list_2[0]
############ Memory Diagram ###########
+-----------+       +-----------------+
| list_1    | ----> | ["B", "C"]      |
+-----------+       +-----------------+
+-----------+       +-----------------+
| list_2    | ----> | ["B", "C"]      |
+-----------+       +-----------------+
+-----------+       +-----------------+
| list_3    | ----> | ["A", "B", "C"] |
+-----------+       +-----------------+
#######################################

print(list_3)  # ["A", "B", "C"]

List comprehension in Python provides a concise way to create lists by applying an expression to each item in an iterable and optionally filtering items based on a condition. It is typically faster than using loops for constructing lists.

SHOW CODE
# [expression for item in iterable if condition]
squares = [x**2 for x in range(10) if x % 2 == 0]
print(squares)  # [0, 4, 16, 36, 64]

Tuple & Dictionary

Tuples are immutable sequences. A tuple with a single item must have a trailing comma (tup = (5, )) to differentiate it from a regular parentheses-enclosed expression.

Tuple unpacking allows extracting elements of a tuple into variables. The number of variables must match the number of elements in the tuple.

SHOW CODE
sides = (1, 2, 2)
a, b, c = sides
print(a, b, c)  # 1 2 2

Dictionaries are mutable and unordered collections of key-value pairs. In Python 3.6x, dictionaries have become ordered by default, meaning the order of item is preserved based on the order of insertion.

Keys in a dictionary must be of a type that is immutable (e.g., strings, numbers, or tuples). Mutable types like lists or dictionaries themselves cannot be used as dictionary keys.

SHOW CODE
valid = {('a', 'b'): 'tuple_key'}  # OK
invalid = {['a', 'b']: 'list_key'}  # TypeError