Skip to content

Latest commit

 

History

History
2446 lines (1898 loc) · 66.6 KB

File metadata and controls

2446 lines (1898 loc) · 66.6 KB

Python

The basics, interpreter, numbers, text, lists, sets, dictionaries, control flow, loops, functions

How to invoke Python interpreter?

You can invoke the Python interpreter in various ways, depending on your operating system and environment. Here’s a guide for different platforms:

  1. From the command line (terminal) Type python3 (or just python if Python 3 is the default) and press Enter.
  2. Using an Integrated Development Environment (IDE)
  3. Executing a Python Script You can directly run a Python script by invoking the interpreter followed by the script name. For example:
python3 script_name.py
  1. Interactive Mode in Python You can also start Python in interactive mode to execute Python commands directly:
  • REPL (Read-Eval-Print Loop): After invoking python or python3 from the command line, you'll be in an interactive environment where you can type Python code and see immediate results.

Example session:

$ python3
Python 3.9.1 (default, Dec 11 2020, 06:28:49) 
[Clang 12.0.0 (clang-1200.0.32.27)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> print("Hello, World!")
Hello, World!
>>> exit()

This starts the Python interpreter, allows you to execute commands, and then exit the interpreter.

Top

Passing arguments to scripts

  1. Using sys.argv
  • The sys.argv list contains the command-line arguments passed to the script. The first element (sys.argv[0]) is the script name, and the subsequent elements are the arguments.
import sys

# Accessing command-line arguments
print("Script name:", sys.argv[0])
print("First argument:", sys.argv[1])
print("Second argument:", sys.argv[2])

# Run this as: python script.py arg1 arg2
  1. Using argparse module
  • The argparse module is more powerful and allows for better control over command-line arguments.
import argparse

# Initialize the parser
parser = argparse.ArgumentParser(description="Process some integers.")

# Adding arguments
parser.add_argument('integers', metavar='N', type=int, nargs='+', help='an integer for the accumulator')

parser.add_argument('--sum', dest='accumulate', action='store_const', const=sum, default=max, help='sum the integers (default: find the max)')

# Parsing arguments
args = parser.parse_args()

print(args.accumulate(args.integers))

# Run this as: python script.py 1 2 3 4 --sum

Top

Using Python as a calculator: Basic arithmetic operations

Python can be used as a powerful calculator directly in its interactive interpreter or by writing simple scripts.

You can perform basic arithmetic operations using Python operators:

# Addition
result = 3 + 5
print(result)  # Outputs: 8

# Subtraction
result = 10 - 4
print(result)  # Outputs: 6

# Multiplication
result = 7 * 6
print(result)  # Outputs: 42

# Division
result = 8 / 2
print(result)  # Outputs: 4.0

# Floor Division (returns an integer)
result = 8 // 3
print(result)  # Outputs: 2

# Modulus (remainder of division)
result = 8 % 3
print(result)  # Outputs: 2

# Exponentiation (power)
result = 2 ** 3
print(result)  # Outputs: 8

Top

Using Python as a calculator: Using the math module

For more advanced mathematical functions, Python provides the math module:

import math

# Square root
result = math.sqrt(16)
print(result)  # Outputs: 4.0

# Trigonometric functions
result = math.sin(math.pi / 2)
print(result)  # Outputs: 1.0

result = math.cos(math.pi)
print(result)  # Outputs: -1.0

# Logarithms
result = math.log(100)  # Natural logarithm (base e)
print(result)  # Outputs: 4.605170185988092

result = math.log10(100)  # Logarithm base 10
print(result)  # Outputs: 2.0

# Factorial
result = math.factorial(5)
print(result)  # Outputs: 120

Top

Using Python as a calculator: Handling complex numbers

Python natively supports complex numbers:

# Define a complex number
z = 2 + 3j

# Real part
real_part = z.real
print(real_part)  # Outputs: 2.0

# Imaginary part
imaginary_part = z.imag
print(imaginary_part)  # Outputs: 3.0

# Conjugate
conjugate = z.conjugate()
print(conjugate)  # Outputs: (2-3j)

# Addition, subtraction, multiplication, division with complex numbers
z1 = 1 + 2j
z2 = 3 - 4j

result = z1 + z2
print(result)  # Outputs: (4-2j)

result = z1 * z2
print(result)  # Outputs: (11+2j)

Top

Using Python as a calculator: eval() for quick calculations

You can use the eval() function to evaluate string expressions as Python code:

expression = "3 * (2 + 7) - 8"
result = eval(expression)
print(result)  # Outputs: 19

Note: Be cautious when using eval() with untrusted input, as it can execute arbitrary code.

Top

Using Python as a calculator: Handling large numbers

Python handles arbitrarily large integers, so you can work with very large numbers without any special libraries:

# Large number multiplication
large_num = 999999999999999999999999 * 888888888888888888888888

print(large_num)

Top

Using Python as a calculator: Floating-point precision

Python provides built-in floating-point precision, but for high-precision calculations, consider using the decimal module:

from decimal import Decimal

# High precision calculation
result = Decimal('0.1') + Decimal('0.2')

print(result)  # Outputs: 0.3

Top

Using Python as a calculator: Built-in functions for common tasks

Python provides many built-in functions for mathematical operations:

  • sum(): Sum of all elements in an iterable.
  • min() and max(): Minimum and maximum of a list of values.
  • round(): Round a number to a given precision.
# Sum of a list
total = sum([1, 2, 3, 4])
print(total)  # Outputs: 10

# Maximum of a list
maximum = max(3, 9, 12, 7)
print(maximum)  # Outputs: 12

# Rounding a number
rounded = round(3.14159, 2)
print(rounded)  # Outputs: 3.14

Top

Basic string operations

Concatenation

You can combine (concatenate) strings using the + operator.

str1 = "Hello"
str2 = "World"
result = str1 + " " + str2
print(result)  # Outputs: Hello World

Repetition

Repeat a string multiple times using the * operator.

str1 = "Hello"
result = str1 * 3
print(result)  # Outputs: HelloHelloHello

Length of a String

Use the len() function to find the length of a string.

str1 = "Hello"
length = len(str1)
print(length)  # Outputs: 5

Top

String indexing and slicing

Indexing

Access individual characters in a string using indexing. Python uses 0-based indexing.

str1 = "Hello"
first_char = str1[0]
last_char = str1[-1]
print(first_char)  # Outputs: H
print(last_char)   # Outputs: o

Slicing

Extract a substring using slicing. The syntax is string[start:end:step].

str1 = "Hello World"
substring = str1[0:5]  # Slices from index 0 to 4
print(substring)  # Outputs: Hello

reversed_str = str1[::-1]  # Reverse the string
print(reversed_str)  # Outputs: dlroW olleH

Top

String methods

Python provides many built-in string methods for common text operations.

Changing case

Convert to uppercase or lowercase.

str1 = "Hello World"
print(str1.upper())  # Outputs: HELLO WORLD
print(str1.lower())  # Outputs: hello world
print(str1.title())  # Outputs: Hello World

Trimming Whitespaces

Remove leading and trailing whitespace using strip(). Use lstrip() and rstrip() to remove from the left or right side only.

str1 = "  Hello World  "
print(str1.strip())
# Outputs: "Hello World"

print(str1.lstrip())
# Outputs: "Hello World  "

print(str1.rstrip())
# Outputs: "  Hello World"

Finding and Replacing Substrings

Locate a substring using find() or index(). Replace a substring with another using replace().

str1 = "Hello World"
position = str1.find("World")
print(position)  # Outputs: 6

replaced_str = str1.replace("World", "Python")
print(replaced_str)  # Outputs: Hello Python

Splitting and Joining Strings

Split a string into a list of substrings using split(). Combine a list of strings into a single string using join().

str1 = "Hello World, Welcome to Python"
words = str1.split()  # Default split by spaces
print(words)  # Outputs: ['Hello', 'World,', 'Welcome', 'to', 'Python']

joined_str = " ".join(words)
print(joined_str)  # Outputs: Hello World, Welcome to Python

Top

Formatting strings

Python offers several ways to format strings:

Old-Style Formatting (%)

name = "Alice"
age = 30
formatted_str = "Name: %s, Age: %d" % (name, age)
print(formatted_str)  # Outputs: Name: Alice, Age: 30

str.format() method

formatted_str = "Name: {}, Age: {}".format(name, age)
print(formatted_str)  # Outputs: Name: Alice, Age: 30

f-Strings (Python 3.6+)

formatted_str = f"Name: {name}, Age: {age}"
print(formatted_str)  # Outputs: Name: Alice, Age: 30

Top

Regular expressions

For more complex text manipulations, Python provides the re module, which supports regular expressions.

Searching with regular expressions

import re

text = "The quick brown fox jumps over the lazy dog."
match = re.search(r'\bfox\b', text)
if match:
    print("Found:", match.group())  # Outputs: Found: fox

Replacing with regular expressions

result = re.sub(r'fox', 'cat', text)
print(result)  # Outputs: The quick brown cat jumps over the lazy dog.

Extracting data

text = "My phone number is 123-456-7890."
phone_number = re.findall(r'\d{3}-\d{3}-\d{4}', text)
print(phone_number)  # Outputs: ['123-456-7890']

Top

Working with multiline strings

You can define and manipulate multiline strings using triple quotes.

multiline_str = """This is
a multiline
string."""
print(multiline_str)

Top

Advanced string manipulations

Reversing a string

str1 = "Python"
reversed_str = str1[::-1]
print(reversed_str)  # Outputs: nohtyP

Checking for substrings

str1 = "Hello World"
contains_hello = "Hello" in str1
print(contains_hello)  # Outputs: True

Counting substrings

str1 = "banana"
count_a = str1.count("a")
print(count_a)  # Outputs: 3

Top

Text encoding and decoding

Python strings are Unicode by default. You can encode and decode strings to/from different encodings.

str1 = "Hello World"
encoded_str = str1.encode('utf-8')
print(encoded_str)  # Outputs: b'Hello World'

decoded_str = encoded_str.decode('utf-8')
print(decoded_str)  # Outputs: Hello World

Top

Creating lists

You can create a list using square brackets [] or the list() function.

Empty List

empty_list = []
empty_list_alternative = list()

List with elements

numbers = [1, 2, 3, 4, 5]
mixed_list = [1, "Hello", 3.14, True]

Top

Accessing list elements

You can access elements in a list using indexing and slicing.

Indexing

Python uses 0-based indexing, meaning the first element is at index 0.

numbers = [1, 2, 3, 4, 5]
first_element = numbers[0]
last_element = numbers[-1]  # Access the last element
print(first_element)  # Outputs: 1
print(last_element)   # Outputs: 5

Slicing

Slicing allows you to extract a portion of the list. The syntax is list[start:end:step].

subset = numbers[1:4]  # Extracts elements from index 1 to 3
print(subset)  # Outputs: [2, 3, 4]

every_other = numbers[::2]  # Extract every second element
print(every_other)  # Outputs: [1, 3, 5]

Top

Modifying lists

Lists are mutable, so you can change their contents.

Changing Elements

numbers[0] = 10
print(numbers)  # Outputs: [10, 2, 3, 4, 5]

Adding elements

  • append(): Adds an element to the end of the list.
  • insert(): Adds an element at a specific position.
  • extend(): Extends the list by appending all the items from another list.
numbers.append(6)  # Adds 6 to the end of the list
print(numbers)  # Outputs: [10, 2, 3, 4, 5, 6]

numbers.insert(1, 15)  # Inserts 15 at index 1
print(numbers)  # Outputs: [10, 15, 2, 3, 4, 5, 6]

numbers.extend([7, 8, 9])  # Adds multiple elements to the end
print(numbers)  # Outputs: [10, 15, 2, 3, 4, 5, 6, 7, 8, 9]

Removing elements

  • remove(): Removes the first occurrence of a value.
  • pop(): Removes and returns the element at the given index (or the last element if no index is specified).
  • clear(): Removes all elements from the list.
numbers.remove(15)  # Removes the first occurrence of 15
print(numbers)  # Outputs: [10, 2, 3, 4, 5, 6, 7, 8, 9]

last_element = numbers.pop()  # Removes and returns the last element
print(last_element)  # Outputs: 9
print(numbers)  # Outputs: [10, 2, 3, 4, 5, 6, 7, 8]

numbers.clear()  # Removes all elements
print(numbers)  # Outputs: []

Top

List operations

Python provides several operations that can be performed on lists.

Concatenation

Combine two lists using the + operator.

list1 = [1, 2, 3]
list2 = [4, 5, 6]
combined_list = list1 + list2
print(combined_list)  # Outputs: [1, 2, 3, 4, 5, 6]

Repetition

Repeat a list multiple times using the * operator.

repeated_list = list1 * 3
print(repeated_list)  # Outputs: [1, 2, 3, 1, 2, 3, 1, 2, 3]

Membership

Check if an item exists in the list using the in keyword.

print(2 in list1)  # Outputs: True
print(10 in list1)  # Outputs: False

Top

Iterating over lists

You can iterate over lists using loops.

For loop

for number in list1:
    print(number)

While loop

i = 0
while i < len(list1):
    print(list1[i])
    i += 1

Top

List comprehensions

List comprehensions offer a concise way to create lists.

squares = [x**2 for x in range(10)]
print(squares)  # Outputs: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

List comprehensions can also include conditional logic.

even_squares = [x**2 for x in range(10) if x % 2 == 0]
print(even_squares)  # Outputs: [0, 4, 16, 36, 64]

Top

Nested lists

Lists can contain other lists, creating a multi-dimensional array.

matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

# Accessing an element in a nested list
element = matrix[1][2]
print(element)  # Outputs: 6

Top

Sorting and reversing lists

Python provides methods to sort and reverse lists.

Sorting

Use sort() to sort the list in place or sorted() to return a new sorted list.

numbers = [3, 1, 4, 1, 5, 9]
numbers.sort()
print(numbers)  # Outputs: [1, 1, 3, 4, 5, 9]

reversed_numbers = sorted(numbers, reverse=True)
print(reversed_numbers)  # Outputs: [9, 5, 4, 3, 1, 1]

Reversing

Use reverse() to reverse the list in place.

numbers.reverse()
print(numbers)  # Outputs: [9, 5, 4, 3, 1, 1]

Top

Copying lists

Copying lists can be done in several ways:

  • Shallow copy: Use the copy() method or slicing.
original_list = [1, 2, 3]
copied_list = original_list.copy()
another_copy = original_list[:]
  • Deep copy: For nested lists, use the copy module to perform a deep copy.
import copy
deep_copied_list = copy.deepcopy(original_list)

Top

List methods

Here are some common list methods:

  • append(x): Adds an item x to the end of the list.
  • extend(iterable): Extends the list by appending elements from an iterable.
  • insert(i, x): Inserts an item x at a given position i.
  • remove(x): Removes the first occurrence of an item x.
  • pop([i]): Removes and returns the item at index i (last item if i is omitted).
  • clear(): Removes all items from the list.
  • index(x, [start, [end]]): Returns the index of the first occurrence of an item x.
  • count(x): Returns the number of occurrences of an item x.
  • sort(key=None, reverse=False): Sorts the list in place.
  • reverse(): Reverses the elements of the list in place.

Top

Basic if statement

The basic syntax of an if statement in Python is:

if condition:
    # code block to execute if the condition is True

Example:

x = 10

if x > 5:
    print("x is greater than 5")

In this example, because x is greater than 5, the message "x is greater than 5" will be printed.

Top

if-else statement

You can use an else clause to specify a block of code to execute if the condition in the if statement is false.

if condition:
    # code block to execute if the condition is True
else:
    # code block to execute if the condition is False

Example:

x = 3

if x > 5:
    print("x is greater than 5")
else:
    print("x is not greater than 5")

In this example, because x is not greater than 5, the message "x is not greater than 5" will be printed.

Top

if-elif-else statement

The elif (short for "else if") statement allows you to check multiple conditions in sequence. The first condition that evaluates to True will have its corresponding code block executed.

if condition1:
    # code block to execute if condition1 is True
elif condition2:
    # code block to execute if condition2 is True
else:
    # code block to execute if neither condition1 nor condition2 is True

Example:

x = 8

if x > 10:
    print("x is greater than 10")
elif x > 5:
    print("x is greater than 5 but less than or equal to 10")
else:
    print("x is 5 or less")

In this example, the message "x is greater than 5 but less than or equal to 10" will be printed because x is 8.

Top

Nested if statements

You can nest if statements within other if, elif or else blocks. This allows you to check multiple conditions at different levels.

if condition1:
    if condition2:
        # code block to execute if both condition1 and condition2 are True
    else:
        # code block to execute if condition1 is True and condition2 is False
else:
    # code block to execute if condition1 is False

Example:

x = 12
y = 5

if x > 10:
    if y > 10:
        print("Both x and y are greater than 10")
    else:
        print("x is greater than 10, but y is not")
else:
    print("x is not greater than 10")

In this example, because x is greater than 10 but y is not, the message "x is greater than 10, but y is not" will be printed.

Top

Comparison operators

These operators are used to compare two values:

  • == Equal to
  • != Not equal to
  • > Greater than
  • < Less than
  • >= Greater than or equal to
  • <= Less than or equal to

Top

Logical operators

These operators are used to combine multiple conditions:

  • and returns True if both conditions are True
  • or returns True if at least one condition is True
  • not reverses the result, returns False if the result is True

Example:

x = 7
y = 10

if x > 5 and y < 15:
    print("x is greater than 5 and y is less than 15")

if x == 7 or y == 12:
    print("Either x is 7 or y is 12")

if not (x == 5):
    print("x is not equal to 5")

Top

Ternary (conditional) operator

Python also supports a conditional expression (often referred to as the ternary operator) that allows you to write an if-else statement in a single line.

value = a if condition else b

Example:

x = 5
result = "Even" if x % 2 == 0 else "Odd"
print(result)  # Outputs: Odd

Top

Basic for loop

The basic syntax of a for loop is:

for variable in sequence:
    # code block to execute for each item in the sequence

Example:

fruits = ["apple", "banana", "cherry"]

for fruit in fruits:
    print(fruit)

In this example, the for loop iterates over the list fruits, and for each item in the list, it prints the item. The output will be:

apple
banana
cherry

Top

Using for with range()

The range() function is commonly used with for loops to generate a sequence of numbers. The syntax of range() is as follows:

  • range(stop): Generates numbers from 0 to stop-1.
  • range(start, stop): Generates numbers from start to stop-1.
  • range(start, stop, step): Generates numbers from start to stop-1, incrementing by step.

Examples:

# Iterating over a range of numbers from 0 to 4
for i in range(5):
    print(i)

Output:

0
1
2
3
4
# Iterating over a range of numbers from 2 to 6
for i in range(2, 7):
    print(i)

Output:

2
3
4
5
6
# Iterating over a range of numbers from 1 to 10, stepping by 2
for i in range(1, 11, 2):
    print(i)

Output:

1
3
5
7
9

Top

Iterating over strings

You can use a for loop to iterate over each character in a string.

Example:

message = "Hello"

for char in message:
    print(char)

Output:

H
e
l
l
o

Top

Iterating over lists of lists (nested loops)

When dealing with lists of lists (like matrices), you can use nested for loops to iterate through each element.

Example:

matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

for row in matrix:
    for element in row:
        print(element, end=" ")
    print()

Output:

1 2 3 
4 5 6 
7 8 9 

Top

Using for with enumerate()

The enumerate() function is useful when you need both the index and the value from the sequence during iteration.

Example:

fruits = ["apple", "banana", "cherry"]

for index, fruit in enumerate(fruits):
    print(f"Index {index}: {fruit}")

Output:

Index 0: apple
Index 1: banana
Index 2: cherry

Top

Using for with zip()

The zip() function allows you to iterate over multiple sequences in parallel.

Example:

names = ["John", "Jane", "Doe"]
ages = [25, 30, 22]

for name, age in zip(names, ages):
    print(f"{name} is {age} years old")

Output:

John is 25 years old
Jane is 30 years old
Doe is 22 years old

Top

for with else

Python allows you to use an else block after a for loop. The else block will execute after the loop finishes, unless the loop is terminated with a break statement.

Example without break:

for i in range(3):
    print(i)
else:
    print("Loop finished")

Output:

0
1
2
Loop finished

Example with break:

for i in range(3):
    if i == 1:
        break
    print(i)
else:
    print("Loop finished")

Output:

0

In this example, the else block does not execute because the loop was terminated early with break.

Top

break and continue in for loops

  • break: Exits the loop immediately.
  • continue: Skips the current iteration and proceeds to the next one.

Example using break:

for i in range(5):
    if i == 3:
        break
    print(i)

Output:

0
1
2

Example using continue:

for i in range(5):
    if i == 3:
        continue
    print(i)

Output:

0
1
2
4

Top

Basic while loop

The basic syntax of a while loop is:

while condition:
    # code block to execute while the condition is True

Here’s a simple example of a while loop:

i = 1

while i <= 5:
    print(i)
    i += 1

Output:

1
2
3
4
5

In this example:

  • The loop starts with i = 1.
  • It prints the value of i and then increments i by 1.
  • The loop continues to execute as long as i is less than or equal to 5.
  • Once i becomes 6, the condition i <= 5 is false, so the loop stops.

Top

pass statement

The pass statement is a null operation; it does nothing when executed. It's used as a placeholder in situations where a statement is syntactically required but no action is needed or when the actual code is not yet implemented.

Creating empty functions or classes

When you're defining a function or class that you haven't implemented yet, you can use pass to avoid syntax errors. This allows you to define the structure of your program without having to write the actual code immediately.

Example:

def my_function():
    pass

class MyClass:
    pass

In this example, my_function and MyClass are placeholders that you can come back to later and implement.

Top

Why use pass?

  • Placeholder for future code: It allows you to outline the structure of your code without implementing the details immediately.

  • Prevent syntax errors: Python requires a code block in certain situations (e.g., after a function or class definition). Using pass ensures that your code is syntactically correct even if you haven’t written the implementation yet.

  • Readability: It makes the intent clear to anyone reading your code that you plan to add code later, or that no action is intentionally required.

Top

pass: Difference from continue and break

  • continue: Skips the rest of the code inside the loop for the current iteration and moves to the next iteration.

  • break: Exits the loop entirely.

  • pass: Does nothing; it simply acts as a placeholder and can be used anywhere a statement is syntactically required.

Top

match statement

The match statement is a feature introduced in Python 3.10 as part of Structural Pattern Matching, which allows for more expressive and readable ways to handle complex conditional logic. It’s similar to the switch statements found in other languages but is more powerful and flexible.

The basic syntax of a match statement is:

match value:
    case pattern1:
        # Code block for pattern1
    case pattern2:
        # Code block for pattern2
    case _:
        # Code block for the default case (similar to 'else' or 'default')

Here's an example that demonstrates how a match statement can be used to handle different cases:

def describe_number(x):
    match x:
        case 0:
            return "Zero"
        case 1:
            return "One"
        case _:
            return "Something else"

print(describe_number(0))  # Output: Zero
print(describe_number(1))  # Output: One
print(describe_number(42)) # Output: Something else

In this example:

  • x is the value being matched.
  • If x is 0, it returns "Zero".
  • If x is 1, it returns "One".
  • The underscore _ is used as a wildcard pattern to match any value not covered by previous cases, acting as the default case.

Top

match statement: Matching multiple patterns

You can match multiple patterns in the same case statement by separating them with a pipe (|), which acts as an "or" operator.

def describe_number(x):
    match x:
        case 0 | 1:
            return "Zero or One"
        case _:
            return "Something else"

print(describe_number(0))  # Output: Zero or One
print(describe_number(1))  # Output: Zero or One
print(describe_number(42)) # Output: Something else

Top

match statement: Matching with conditions (guards)

You can add an if condition (called a "guard") to a case statement to further refine the match.

def describe_number(x):
    match x:
        case 0:
            return "Zero"
        case _ if x > 0:
            return "Positive"
        case _:
            return "Negative"

print(describe_number(10))  # Output: Positive
print(describe_number(-5))  # Output: Negative

Top

Destructuring in match statements: Tuples

match statements can also destructure sequences and objects, making it possible to directly match complex data structures.

def point_location(point):
    match point:
        case (0, 0):
            return "Origin"
        case (x, 0):
            return f"On the X-axis at {x}"
        case (0, y):
            return f"On the Y-axis at {y}"
        case (x, y):
            return f"At coordinates ({x}, {y})"
        case _:
            return "Not a point"

print(point_location((0, 0)))    # Output: Origin
print(point_location((3, 0)))    # Output: On the X-axis at 3
print(point_location((0, 4)))    # Output: On the Y-axis at 4
print(point_location((2, 5)))    # Output: At coordinates (2, 5)

Top

Destructuring in match statements: Lists

def list_info(lst):
    match lst:
        case []:
            return "Empty list"
        case [x]:
            return f"List with one element: {x}"
        case [x, y]:
            return f"List with two elements: {x}, {y}"
        case [x, *rest]:
            return f"List starts with {x} and has more elements: {rest}"
        case _:
            return "Unknown list format"

# Output: Empty list
print(list_info([]))

# Output: List with one element: 1
print(list_info([1]))

# Output: List with two elements: 1, 2
print(list_info([1, 2])) 

# Output: List starts with 1 and has more elements: [2, 3, 4]
print(list_info([1, 2, 3, 4]))

Top

match: Matching named attributes in objects

You can match against attributes of objects, which is useful for working with classes.

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

def describe_point(point):
    match point:
        case Point(0, 0):
            return "Origin"

        case Point(x, y):
            return f"Point at ({x}, {y})"

p1 = Point(0, 0)
p2 = Point(3, 4)

print(describe_point(p1))  # Output: Origin

print(describe_point(p2))  # Output: Point at (3, 4)

Top

Basic function definition

The basic syntax for defining a function in Python is:

def function_name(parameters):
    """Docstring (optional): A brief description of what the function does."""
    # Code block
    return result  # (optional)
  • def: A keyword used to start the function definition.

  • function_name: The name you give to the function. Function names should follow naming conventions, typically using lowercase letters and underscores.

  • parameters: Variables listed inside the parentheses, representing inputs to the function.

  • return: (Optional) A statement used to return a value from the function.

Top

Example of a simple function

Here’s an example of a simple function that adds two numbers:

def add_numbers(a, b):
    """This function adds two numbers and returns the result."""
    result = a + b
    return result

You can call this function like so:

sum_result = add_numbers(5, 3)
print(sum_result)  # Output: 8

Top

Function with default parameters

You can define default values for function parameters. If a caller doesn’t provide a value for a parameter, the default value is used.

def greet(name="Guest"):
    """This function greets the person with the given name."""
    return f"Hello, {name}!"

# Calling the function with an argument
print(greet("Alice"))  # Output: Hello, Alice!

# Calling the function without an argument
print(greet())  # Output: Hello, Guest!

Top

Using *args for variable length positional arguments

Python allows functions to accept a variable number of arguments using *args for positional arguments.

def sum_all(*args):
    """This function returns the sum of all the provided arguments."""
    return sum(args)

print(sum_all(1, 2, 3))  # Output: 6
print(sum_all(10, 20, 30, 40))  # Output: 100

Top

Using **kwargs for variable length keyword arguments

Python allows functions to accept a variable number of arguments using **kwargs for keyword arguments.

def print_details(**kwargs):
    """This function prints details provided as keyword arguments."""
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_details(name="Alice", age=25, city="New York")
# Output:
# name: Alice
# age: 25
# city: New York

Top

Returning multiple values from a function

Python functions can return multiple values as a tuple:

def calculate(a, b):
    """This function returns the sum and difference of two numbers."""
    sum_result = a + b
    diff_result = a - b
    return sum_result, diff_result

sum_val, diff_val = calculate(10, 5)
print(f"Sum: {sum_val}, Difference: {diff_val}")
# Output: Sum: 15, Difference: 5

Top

Lambda functions

Lambda functions are small anonymous functions defined with the lambda keyword. They can have any number of input parameters but only one expression.

# A lambda function to add two numbers
add = lambda x, y: x + y

# Using the lambda function
print(add(3, 5))  # Output: 8

Lambda functions are often used for short, simple functions that are passed as arguments to other functions e.g., in map(), filter() or sorted().

Top

Docstrings

A docstring is a string that describes what the function does. It’s placed as the first statement in the function body and is enclosed in triple quotes. Docstrings are useful for documenting code and can be accessed using the help() function or the __doc__ attribute.

def multiply(a, b):
    """This function multiplies two numbers and returns the result."""
    return a * b

# Accessing the docstring
print(multiply.__doc__)

Top

Nested functions

Functions can be defined inside other functions, creating nested functions. Nested functions are useful for encapsulating helper functions or creating closures.

def outer_function(x):
    """This is the outer function."""

    def inner_function(y):
        """This is the inner function."""
        return x + y

    return inner_function

# Using the outer function to get an inner function
add_five = outer_function(5)
print(add_five(10))  # Output: 15

Top

Closures

A closure is a function object that remembers values in enclosing scopes even if they are not present in memory.

def outer_function(x):
    """This function returns a function that adds x to its argument."""
    
    def inner_function(y):
        return x + y
    
    return inner_function

add_ten = outer_function(10)
print(add_ten(5))  # Output: 15

In this example, add_ten is a closure that remembers the value 10 for x, even though outer_function has finished execution.

Top

Decorators

Decorators are functions that modify the behavior of another function. They are often used for logging, access control, memoization, and more. A decorator is a function that takes in a function as an input argument and returns a supplemented copy of that function.

Decorators can be nested and decorators allow you to increase/expand the already implemented functionality. You can also pass arguments to the decorated function or the decorator itself.

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()
# Output:
# Something is happening before the function is called.
# Hello!
# Something is happening after the function is called.

Top

Using lists as stacks

In Python, lists are versatile data structures that can be used as stacks. A stack is a collection of elements that follows the Last In, First Out (LIFO) principle, meaning that the last element added is the first one to be removed.

Basic stack operations with lists

Python lists provide built-in methods to implement stack operations:

  • append() Adds an element to the top of the stack.
  • pop() Removes and returns the element from the top of the stack.

Top

Pushing an item onto the stack

To add an item to the stack, use the append() method. This operation pushes an item onto the end of the list, which is considered the top of the stack.

Example:

stack = []

# Push items onto the stack
stack.append(1)
stack.append(2)
stack.append(3)

print(stack)  # Output: [1, 2, 3]

In this example, the elements 1, 2 and 3 are added to the stack.

Top

Popping an item from the stack

To remove and return the item from the top of the stack, use the pop() method. This operation removes the last element in the list.

Example:

# Pop items from the stack
top_item = stack.pop()
print(top_item)  # Output: 3

print(stack)  # Output: [1, 2]

In this example, 3 is the last element added to the stack, so it is the first one to be removed.

Top

Checking if the stack is empty

Before popping an item from the stack, it's good practice to check if the stack is empty to avoid errors.

Example:

if stack:
    top_item = stack.pop()
    print("Popped item:", top_item)
else:
    print("Stack is empty")

Top

Peeking at the top item

If you want to look at the top item of the stack without removing it, you can simply access the last item in the list using stack[-1].

Example:

# Peek at the top item without popping it
if stack:
    top_item = stack[-1]
    print("Top item:", top_item)  # Output: 2
else:
    print("Stack is empty")

Top

Complete stack example

Here’s a complete example that demonstrates pushing, popping, and peeking:

# Initialize an empty stack
stack = []

# Push items onto the stack
stack.append('A')
stack.append('B')
stack.append('C')

print("Stack after pushing:", stack)  # Output: ['A', 'B', 'C']

# Peek at the top item
if stack:
    print("Top item:", stack[-1])  # Output: 'C'

# Pop an item from the stack
top_item = stack.pop()
print("Popped item:", top_item)  # Output: 'C'
print("Stack after popping:", stack)  # Output: ['A', 'B']

# Pop all items
while stack:
    print("Popped:", stack.pop())

print("Stack is now empty:", stack)  # Output: []

Top

Stack underflow

If you try to pop an item from an empty stack, Python will raise an IndexError. You can handle this using a try-except block or by checking if the stack is empty before popping.

Example:

try:
    stack.pop()
except IndexError:
    print("Cannot pop from an empty stack!")

Top

Sets

A set is an unordered collection of unique elements. Sets are useful when you need to store items without duplicates and when you require fast membership testing (i.e., checking whether an item is in the set). Sets are also ideal for performing mathematical operations like union, intersection, difference, and symmetric difference.

Creating sets

Sets can be created using curly braces {} or the set() function. However, to create an empty set, you must use set() as {} creates an empty dictionary.

Example:

# Creating a set with some elements
fruits = {"apple", "banana", "cherry"}
print(fruits)  # Output: {'apple', 'banana', 'cherry'}

# Creating an empty set
empty_set = set()
print(empty_set)  # Output: set()

# Using the set() function to create a set from an iterable
letters = set("hello")
print(letters)  # Output: {'h', 'e', 'l', 'o'}

Top

Sets: Adding elements

You can add a single element to a set using the add() method or multiple elements using the update() method.

# Adding a single element
fruits.add("orange")
print(fruits)  # Output: {'apple', 'banana', 'cherry', 'orange'}

# Adding multiple elements
fruits.update(["mango", "grape"])
print(fruits)  # Output: {'banana', 'cherry', 'grape', 'mango', 'orange', 'apple'}

Top

Sets: Removing elements

You can remove elements using remove() or discard(). The difference is that remove() will raise a KeyError if the element is not found, while discard() will not.

# Removing an element
fruits.remove("banana")
print(fruits)  # Output: {'cherry', 'grape', 'mango', 'orange', 'apple'}

# Discarding an element
fruits.discard("apple")
print(fruits)  # Output: {'cherry', 'grape', 'mango', 'orange'}

# Trying to remove a non-existent element with discard (no error)
fruits.discard("banana")  # No error

Top

Sets: Clearing all elements

You can remove all elements from a set using the clear() method.

fruits.clear()
print(fruits)  # Output: set()

Top

Set operations: Union (| or union())

Combines elements from both sets, without duplicates.

set1 = {1, 2, 3}
set2 = {3, 4, 5}

union_set = set1 | set2
# or
union_set = set1.union(set2)
print(union_set)  # Output: {1, 2, 3, 4, 5}

Top

Set operations: Intersection (& or intersection())

Returns elements that are common to both sets.

set1 = {1, 2, 3}
set2 = {3, 4, 5}

intersection_set = set1 & set2
# or
intersection_set = set1.intersection(set2)

print(intersection_set)  # Output: {3}

Top

Set operations: Difference (- or difference())

Returns elements that are in the first set but not in the second.

set1 = {1, 2, 3}
set2 = {3, 4, 5}

difference_set = set1 - set2
# or
difference_set = set1.difference(set2)

print(difference_set)  # Output: {1, 2}

Top

Set operations: Symmetric difference (^ or symmetric_difference())

Returns elements that are in either set, but not in both.

set1 = {1, 2, 3}
set2 = {3, 4, 5}

symmetric_difference_set = set1 ^ set2
# or
symmetric_difference_set = set1.symmetric_difference(set2)

print(symmetric_difference_set)  # Output: {1, 2, 4, 5}

Top

Set operations: Useful set methods

  • len(set) Returns the number of elements in the set.

  • in keyword Checks if an element is in the set.

  • issubset() Checks if the set is a subset of another set.

  • issuperset() Checks if the set is a superset of another set.

  • copy() Creates a shallow copy of the set.

Top

Frozen sets

A frozen set is an immutable version of a set. Once created, elements cannot be added or removed. Frozen sets are useful for situations where you need an immutable set, such as using it as a key in a dictionary.

Example:

frozen_set = frozenset([1, 2, 3, 4])
print(frozen_set)  # Output: frozenset({1, 2, 3, 4})

# Attempting to modify a frozen set will raise an error
# frozen_set.add(5)  # Raises AttributeError

Top

Dictionaries

A dictionary is a collection of key-value pairs. Each key is associated with a value, and the key acts as a unique identifier for that value within the dictionary. Dictionaries are one of the most commonly used data structures in Python because they allow for efficient lookups, insertions, and deletions based on keys.

Creating a dictionary

You can create a dictionary using curly braces {} with key-value pairs separated by colons :. Alternatively, you can use the dict() function.

Examples:

# Creating a dictionary with curly braces
person = {
    "name": "Alice",
    "age": 30,
    "city": "New York"
}

# Creating a dictionary using the dict() function
person2 = dict(name="Bob", age=25, city="Los Angeles")

print(person)  # Output: {'name': 'Alice', 'age': 30, 'city': 'New York'}
print(person2) # Output: {'name': 'Bob', 'age': 25, 'city': 'Los Angeles'}

Top

Dictionaries: Accessing values

You can access the value associated with a specific key using the square bracket notation [] or the get() method.

Examples:

# Accessing values using square brackets
print(person["name"])  # Output: Alice

# Accessing values using the get() method
print(person.get("age"))  # Output: 30

# Using get() with a default value if the key is not found
print(person.get("address", "Unknown"))  # Output: Unknown

Top

Dictionaries: Adding and updating elements

You can add new key-value pairs or update the value of an existing key by assigning a value to the key using square brackets.

Examples:

# Adding a new key-value pair
person["email"] = "alice@example.com"
print(person)  # Output: {'name': 'Alice', 'age': 30, 'city': 'New York', 'email': 'alice@example.com'}

# Updating the value of an existing key
person["age"] = 31
print(person)  # Output: {'name': 'Alice', 'age': 31, 'city': 'New York', 'email': 'alice@example.com'}

Top

Dictionaries: Removing elements

You can remove elements from a dictionary using the del statement, the pop() method or the popitem() method.

Examples:

# Removing an element using del
del person["email"]
print(person)  # Output: {'name': 'Alice', 'age': 31, 'city': 'New York'}

# Removing an element using pop()
age = person.pop("age")
print(age)     # Output: 31
print(person)  # Output: {'name': 'Alice', 'city': 'New York'}

# Removing the last inserted key-value pair using popitem()
last_item = person.popitem()
print(last_item)  # Output: ('city', 'New York')
print(person)     # Output: {'name': 'Alice'}

Top

Dictionaries: Useful methods

  • keys() Returns a view object that displays a list of all the keys in the dictionary.

  • values() Returns a view object that displays a list of all the values in the dictionary.

  • items() Returns a view object that displays a list of key-value pairs (tuples) in the dictionary.

  • update() Updates the dictionary with the key-value pairs from another dictionary or iterable of key-value pairs.

Top

Dictionaries: Checking for keys

You can check if a key exists in a dictionary using the in keyword.

if "name" in person:
    print("Name is in the dictionary")

Top

Dictionaries: Iterating over a dictionary

You can iterate over keys, valuesor key-value pairs in a dictionary using afor` loop.

Examples:

# Iterating over keys
for key in person.keys():
    print(key)

# Iterating over values
for value in person.values():
    print(value)

# Iterating over key-value pairs
for key, value in person.items():
    print(f"{key}: {value}")

Top

Dictionaries: Dictionary comprehensions

You can create dictionaries using dictionary comprehensions, which provide a concise way to create dictionaries from iterables.

Example:

squares = {x: x**2 for x in range(1, 6)}
print(squares)  # Output: {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

Top

Dictionaries: Nested dictionaries

A dictionary can contain another dictionary, allowing for complex data structures.

Example:

students = {
    "Alice": {"age": 25, "grade": "A"},
    "Bob": {"age": 22, "grade": "B"}
}

print(students["Alice"]["grade"])  # Output: A

Top

Dictionaries: Copying dictionaries

To create a copy of a dictionary, use the copy() method or the dict() function. Simply using the assignment operator = will create a reference to the same dictionary, not a copy.

Example:

original = {"one": 1, "two": 2}
copy1 = original.copy()
copy2 = dict(original)

# Modifying the copy won't affect the original
copy1["one"] = "ONE"
print(original)  # Output: {'one': 1, 'two': 2}
print(copy1)     # Output: {'one': 'ONE', 'two': 2}

Top

Python modules

In Python, modules are files containing Python code (functions, classes, variables, etc.) that can be reused in other programs. Using modules helps organize code into logical units and promotes code reuse. A Python module is simply a .py file that can be imported into another Python file to access its functions, classes, and variables.

Creating a module

To create a module, you just need to write Python code in a file and save it with a .py extension.

Example: Let's say you have a file named mymodule.py with the following code:

# mymodule.py

def greet(name):
    return f"Hello, {name}!"

pi = 3.14159

Here, mymodule.py contains a function greet() and a variable pi.

Top

Importing a module

You can import a module into another Python file using the import statement.

Example:

# main.py

import mymodule

# Accessing the function and variable from mymodule
message = mymodule.greet("Alice")
print(message)  # Output: Hello, Alice!

print(mymodule.pi)  # Output: 3.14159

In this example, the mymodule module is imported, and its function greet() and variable pi are accessed.

Top

Different ways to import modules

There are several ways to import modules or specific items from modules.

  1. Importing the entire module
import mymodule

You need to use the module name when calling its functions or variables, like mymodule.greet().

  1. Importing specific functions or variables You can import specific items from a module using the from ... import syntax.
from mymodule import greet, pi

# Now you can use greet and pi directly
print(greet("Bob")) # Output: Hello, Bob!
print(pi)  # Output: 3.14159
  1. Importing all items from a module You can import everything from a module using the from ... import * syntax, but this is generally not recommended because it can lead to conflicts with existing names.
from mymodule import *

# Now you can use everything from mymodule without prefixing it
print(greet("Eve"))  # Output: Hello, Eve!
print(pi)            # Output: 3.14159
  1. Using aliases You can assign an alias to a module using the as keyword to shorten the module name or avoid conflicts.
import mymodule as mm

# Using the alias to call the function
print(mm.greet("Charlie"))  # Output: Hello, Charlie!

Top

Built-in Python modules

Python comes with many built-in modules that provide common functionality. Some common built-in modules are:

  • math: Provides mathematical functions.
  • random: Provides functions for generating random numbers.
  • os: Provides functions for interacting with the operating system.
  • sys: Provides access to system-specific parameters and functions.
  • datetime: Provides classes for manipulating dates and times.

Example of using built-in modules:

import math
import random

# Output: 4.0
print(math.sqrt(16))

# Generate a random number between 1 and 10
print(random.randint(1, 10))

Top

Installing third-party modules

Python has a vast ecosystem of third-party modules that you can install using pip, the Python package manager. These modules are hosted on the Python Package Index (PyPI) and can be installed using the command:

pip install <module-name>

For example, to install the popular requests module for making HTTP requests, run:

pip install requests

Once installed, you can import the module into your code:

import requests

response = requests.get("https://api.github.com")
print(response.status_code)  # Output: 200 (if successful)

Top

Finding the location of modules

You can find where a module is located on your system by checking the __file__ attribute.

Example:

import os

print(os.__file__)  # Outputs the path to the os module

Top

Module search path (sys.path)

When you import a module, Python searches for it in directories listed in the sys.path variable. This includes:

  • The directory containing the current script.
  • The directories listed in the PYTHONPATH environment variable (if set).
  • The default directories where Python is installed.

You can view the search path by importing the sys module and printing sys.path.

Example:

import sys

print(sys.path)  # Outputs the list of directories Python searches for modules

Top

Module initialization code

When a module is imported, all top-level code (not inside functions or classes) in the module is executed. You can use this feature to initialize data when the module is imported.

Example:

# mymodule.py
print("Module mymodule has been imported")

def greet(name):
    return f"Hello, {name}!"

When you import mymodule, you'll see:

import mymodule
# Output: Module mymodule has been imported

Top

Packages

A package is a way to organize related modules into directories. A package is essentially a directory containing a special __init__.py file and multiple modules.

Example structure:

mypackage/
    __init__.py
    module1.py
    module2.py

In this case, mypackage is a package and module1.py and module2.py are modules inside that package. You can import modules from the package like this:

from mypackage import module1

Top

Defining a class

Classes are used to define new types of objects, encapsulating both data (attributes) and behavior (methods) into a single structure. Python classes are a fundamental aspect of Object-Oriented Programming (OOP), which helps in structuring code to make it more modular, reusable, and scalable.

Defining a class

A class is defined using the class keyword, followed by the class name and a colon. Inside the class, methods (functions) and attributes (variables) are defined.

Basic class syntax

class MyClass:
    # Class attribute (shared by all instances)
    class_attribute = "I am a class attribute"
    
    # Constructor method (called when a new instance is created)
    def __init__(self, attribute_value):
        # Instance attribute (unique to each instance)
        self.instance_attribute = attribute_value

    # Instance method (operates on an instance)
    def display(self):
        print(f"Class attribute: {self.class_attribute}")
        print(f"Instance attribute: {self.instance_attribute}")

In this example:

  • class_attribute: A class attribute that is shared by all instances of the class.

  • __init__: The constructor method, which is called when an instance (object) of the class is created. It initializes the instance with attributes.

  • self: Refers to the current instance of the class. It must be the first parameter in instance methods to access instance-specific data.

Top

Creating objects (instances)

You can create an instance (object) of a class by calling the class name like a function. This will invoke the __init__ method to initialize the object.

Example: Creating an object

# Creating an object of MyClass
obj = MyClass("Instance attribute value")

# Accessing methods and attributes
obj.display()
# Output: Class attribute: I am a class attribute
# Instance attribute: Instance attribute value

In this example, obj is an instance of MyClass. The method display() prints both the class and instance attributes.

Top

Instance attributes vs. class attributes

  • Class attributes are shared by all instances of the class. Changing the value of a class attribute will affect all instances.
  • Instance attributes are unique to each instance. Changing the value of an instance attribute will affect only that specific instance.

Example of class and instance attributes

class Car:
    wheels = 4  # Class attribute (all cars have 4 wheels)

    def __init__(self, color):
        self.color = color  # Instance attribute (each car has its own color)

# Creating two objects
car1 = Car("red")
car2 = Car("blue")

# Accessing attributes
print(car1.color)  # Output: red
print(car2.color)  # Output: blue

# Accessing class attribute
print(car1.wheels)  # Output: 4
print(car2.wheels)  # Output: 4

Top

Instance methods

Instance methods are functions defined inside a class that operate on instances of the class. They must take self as the first parameter, which refers to the instance on which the method is called.

Example: Instance method

class Dog:
    def __init__(self, name):
        self.name = name
    
    def bark(self):
        print(f"{self.name} is barking!")

# Creating an instance and calling an instance method
dog = Dog("Buddy")
dog.bark()  # Output: Buddy is barking!

Top

Class methods

A class method operates on the class itself rather than on instances. To define a class method, use the @classmethod decorator. The first parameter of a class method is cls, which refers to the class.

Example: Class method

class Animal:
    species_count = 0

    def __init__(self, name):
        self.name = name
        Animal.species_count += 1

    @classmethod
    def total_species(cls):
        print(f"Total species count: {cls.species_count}")

# Creating instances
a1 = Animal("Lion")
a2 = Animal("Tiger")

# Calling class method
Animal.total_species()  # Output: Total species count: 2

Top

Static methods

A static method doesn’t access or modify the class or instance data. It behaves like a regular function but belongs to the class’s namespace. Static methods are defined using the @staticmethod decorator.

Example: Static method

class MathOperations:
    @staticmethod
    def add(x, y):
        return x + y

# Calling static method
result = MathOperations.add(5, 3)
print(result)  # Output: 8

Top

Inheritance

Python supports inheritance, which allows one class to inherit attributes and methods from another class. This is useful for creating a hierarchy of classes that share behavior.

  • Base class (parent): The class being inherited from.
  • Derived class (child): The class that inherits from the base class.

Example: Inheritance

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        print(f"{self.name} makes a sound.")

# Derived class
class Dog(Animal):
    def speak(self):
        print(f"{self.name} barks.")

# Using inheritance
dog = Dog("Buddy")
dog.speak()  # Output: Buddy barks.

Here, the Dog class inherits from the Animal class and overrides the speak() method.

Top

Method overriding

When a derived class provides a specific implementation of a method already defined in the base class, it overrides the base class method. The overridden method will be called instead of the base class method.

Example: Method overriding

class Animal:
    def speak(self):
        print("Animal sound")

class Dog(Animal):
    def speak(self):
        print("Dog barks")

dog = Dog()
dog.speak()  # Output: Dog barks

Top

The super() function

The super() function allows you to call methods from the parent class in a derived class. This is useful when you want to extend the functionality of a method in the derived class but still use the base class’s implementation.

Example: Using super()

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        print(f"{self.name} makes a sound.")

class Dog(Animal):
    def __init__(self, name, breed):
        # Call the parent class's constructor
        super().__init__(name)
        self.breed = breed

    def speak(self):
        # Call the parent class's speak method
        super().speak()
        print(f"{self.name} barks.")

dog = Dog("Buddy", "Golden Retriever")
dog.speak()

Top

Encapsulation

In Python, data encapsulation is achieved by restricting access to certain attributes and methods using leading underscores _ or __.

  • Single leading underscore (_attribute): Protected, a convention to indicate that the attribute is intended for internal use.
  • Double leading underscores (__attribute): Private, name mangling is applied, making the attribute harder to access from outside the class.

Example: Encapsulation

class Person:
    def __init__(self, name, age):
        self.name = name
        self._age = age  # This is meant to be "protected"

    def display(self):
        print(f"Name: {self.name}, Age: {self._age}")

person = Person("John", 30)
person.display()

print(person._age)  # Accessing protected attribute (though it's just a convention)

Top

What is LEGB Rule?

The LEGB Rule in Python describes the order in which Python looks for variable names when you reference them. It’s about scope resolution — that is, where Python searches for a name you use in your code.

  • Local - Names assigned inside a function (or lambda) — the innermost scope.
  • Enclosing - Names in the local scope of any and all enclosing functions, starting from the innermost.
  • Global - Names assigned at the top-level of a module or declared as global inside a function.
  • Built-in - Names preassigned in Python's built-in scope (like len(), sum(), etc.).

If Python can’t find the name in any of these places, it will raise a NameError.

Important:

  • You can read variables from outer scopes without declaring anything special.
  • But if you assign a variable inside a function, it becomes local to that function by default.
    • Use global to assign to a global variable inside a function.
    • Use nonlocal to assign to an enclosing variable inside a nested function.

Top

Namespaces vs. Scopes

  • A namespace is a mapping of names to objects. Namespaces define what names exist.
  • A scope determines the visibility and lifetime of those names. Scopes define where you can access a name.

Top