Skip to main content

Python Basics

Status

This note is complete, reviewed, and considered stable.

Variables and Data Types​

Variables: Variables in Python are used to store data and do not require explicit declaration. They are created when a value is assigned.

age = 25 # Integer
name = "Alice" # String
is_student = True # Boolean

Data Types:

  • int: Integer, e.g., 5
  • float: Floating-point number, e.g., 5.0
  • str: String, e.g., "Hello"
  • bool: Boolean, e.g., True or False
  • NoneType: Represents the absence of value, None

Basic Operators​

Arithmetic Operators: +, -, *, /, // (floor division), % (modulus), ** (exponent)

Comparison Operators: ==, !=, <, >, <=, >=

Logical Operators: and, or, not

Precedence and Associativity​

In Python, precedence determines the order in which operators are evaluated in an expression, while associativity defines the direction in which operators of the same precedence level are evaluated.

Operator Precedence​

Operators with higher precedence are evaluated before operators with lower precedence. For example:

result = 10 + 5 * 2 # Multiplication (*) has higher precedence than addition (+)
print(result) # Output: 20

Here, 5 * 2 is evaluated first, and then 10 + 10.

Operator Associativity​

When operators of the same precedence appear together, associativity determines their evaluation order:

  • Left-to-right associativity: Most operators (e.g., +, -, *, /, etc.) are evaluated from left to right.

    result = 10 - 5 + 2 # Evaluated as (10 - 5) + 2
    print(result) # Output: 7
  • Right-to-left associativity: Some operators (e.g., ** for exponentiation and assignment operators like =) are evaluated from right to left.

    result = 2 ** 3 ** 2 # Evaluated as 2 ** (3 ** 2)
    print(result) # Output: 512

Parentheses​

To explicitly define the evaluation order, we can use parentheses:

result = (10 + 5) * 2 # Parentheses override default precedence
print(result) # Output: 30

Operator Precedence and Associativity Table​

OperatorDescriptionAssociativity
()ParenthesesN/A (Highest precedence)
**ExponentiationRight-to-left
+x, -x, ~xUnary plus, minus, bitwise NOTRight-to-left
*, /, //, %Multiplication, division, floor division, moduloLeft-to-right
+, -Addition, subtractionLeft-to-right
<<, >>Bitwise shift operatorsLeft-to-right
&Bitwise ANDLeft-to-right
^Bitwise XORLeft-to-right
``Bitwise OR
==, !=, >, >=, <, <=, is, is not, in, not inComparison and membership operatorsLeft-to-right
notLogical NOTRight-to-left
andLogical ANDLeft-to-right
orLogical ORLeft-to-right
=, +=, -=, *=, /=, %=, **=, //=Assignment operatorsRight-to-left
lambdaLambda expressionsN/A (Lowest precedence)

List Comprehension​

A concise way to create lists based on existing iterables.

squares = [x**2 for x in range(10)]

Basic I/O​

Input: input() function to take user input.

name = input("Enter our name: ")

Output: print() function to display output.

print("Hello, World!")

Mutation​

Key Points​

  1. Mutable Data Structures:

    • Can be modified after creation.
    • Examples: list, dict, set, bytearray.
  2. Immutable Data Structures:

    • Cannot be modified after creation.
    • Examples: tuple, str, frozenset, bytes, range.

Here is a table summarizing common Python data structures and whether they are mutable or immutable:

Data StructureMutableDescription
List (list)YesCan be modified after creation (e.g., adding, removing, or changing elements).
Tuple (tuple)NoImmutable sequence; cannot be changed after creation.
Dictionary (dict)YesKeys and values can be added, removed, or updated.
Set (set)YesCan add or remove elements; unordered collection of unique elements.
String (str)NoImmutable sequence of characters; cannot be modified after creation.
Frozen Set (frozenset)NoImmutable version of set; cannot add or remove elements.
Bytes (bytes)NoImmutable sequence of bytes (binary data).
Bytearray (bytearray)YesMutable version of bytes; can modify individual bytes.
Range (range)NoImmutable sequence representing a range of numbers.

Unpacking Symbol in Python​

In Python, unpacking is a mechanism that allows us to assign values from an iterable (like a list, tuple, or dictionary) to multiple variables in a single step. The unpacking operation is done using the * and ** symbols. Below is a detailed explanation of their usage:

Unpacking with * (Single Asterisk)​

The * symbol is used for unpacking elements from iterables (like lists, tuples, or sets).

Unpacking Elements into Variables​

The * symbol can capture multiple elements from an iterable into a list.

Example​
# Unpacking a list
a, *b, c = [1, 2, 3, 4, 5]
print(a) # 1
print(b) # [2, 3, 4]
print(c) # 5
Explanation​
  • a gets the first element.
  • c gets the last element.
  • *b captures all the elements in the middle.

Function Arguments Unpacking​

The * symbol can unpack an iterable (like a list or tuple) and pass its elements as arguments to a function.

Example​
def add(a, b, c):
return a + b + c

args = [1, 2, 3]
result = add(*args) # Unpacks the list [1, 2, 3] into a, b, c
print(result) # 6

Unpacking in List or Set Literals​

We can use * to unpack elements into a new list or set.

Example​
# List Unpacking
a = [1, 2, 3]
b = [*a, 4, 5]
print(b) # [1, 2, 3, 4, 5]

# Set Unpacking
s1 = {1, 2, 3}
s2 = {*s1, 4, 5}
print(s2) # {1, 2, 3, 4, 5}

Unpacking for zip() in Matrix Transposition​

The * operator can unpack rows of a matrix to pass them as arguments to the zip() function.

Example​
matrix = [[1, 2], [3, 4], [5, 6]]
transposed = list(zip(*matrix))
print(transposed) # [(1, 3, 5), (2, 4, 6)]

Collecting Excess Positional Arguments​

In function definitions, the * symbol allows us to collect extra positional arguments into a tuple.

Example​
def func(a, *args):
print(a) # First argument
print(args) # Remaining arguments as a tuple

func(1, 2, 3, 4) # Output: 1 and (2, 3, 4)

Unpacking with ** (Double Asterisk)​

The ** symbol is used for unpacking dictionaries into key-value pairs.

Function Keyword Argument Unpacking​

We can use ** to unpack a dictionary and pass its key-value pairs as keyword arguments to a function.

Example​
def greet(name, age):
print(f"Hello {name}, we are {age} years old.")

data = {"name": "Alice", "age": 25}
greet(**data) # Equivalent to: greet(name="Alice", age=25)

Merging Dictionaries​

We can use ** to unpack multiple dictionaries into a new dictionary.

Example​
d1 = {"a": 1, "b": 2}
d2 = {"c": 3, "d": 4}
merged = {**d1, **d2}
print(merged) # {'a': 1, 'b': 2, 'c': 3, 'd': 4}

Collecting Keyword Arguments​

In function definitions, ** allows us to collect excess keyword arguments into a dictionary.

Example​
def func(a, **kwargs):
print(a) # First argument
print(kwargs) # Remaining keyword arguments as a dictionary

func(1, b=2, c=3) # Output: 1 and {'b': 2, 'c': 3}

Combining * and ** in Functions​

We can use both * and ** together in function definitions to handle both extra positional and keyword arguments.

Example​

def func(a, *args, **kwargs):
print(a) # First argument
print(args) # Extra positional arguments
print(kwargs) # Extra keyword arguments

func(1, 2, 3, b=4, c=5)
# Output:
# 1
# (2, 3)
# {'b': 4, 'c': 5}

Unpacking in For Loops​

We can unpack iterables while iterating through them in a loop.

Example​

pairs = [(1, 2), (3, 4), (5, 6)]
for a, b in pairs:
print(a, b)
# Output:
# 1 2
# 3 4
# 5 6

Nested Unpacking​

Python allows unpacking within nested structures.

Example​

nested = [1, (2, 3), 4]
a, (b, c), d = nested
print(a, b, c, d) # 1 2 3 4

Summary of * and ** Usage​

SymbolUse CaseDescription
*Unpacking IterablesUsed to unpack lists, tuples, sets, or other iterables.
*Collect Positional ArgumentsCollects extra positional arguments into a tuple.
*List/Set UnpackingUnpacks iterables into new lists or sets.
*For zip() Matrix TranspositionUnpacks rows for transposition.
**Unpacking DictionariesUsed to unpack key-value pairs into function calls or new dictionaries.
**Collect Keyword ArgumentsCollects extra keyword arguments into a dictionary.