Skip to content

Latest commit

 

History

History
1270 lines (915 loc) · 45.2 KB

File metadata and controls

1270 lines (915 loc) · 45.2 KB

Basic Python Programming

Table of Contents

I - Introduction to Python

  1. What is Python?
  2. Python Installation
  3. Running Python Code

II - Basic Syntax

  1. Variables
  2. Comments
  3. Using Functions
  4. Basic Data Types
  5. Common Operators
  6. String Manipulation

III - Data Structures

  1. Lists
  2. Tuples
  3. Dictionaries
  4. Sets
  5. Common Operators and Functions for Data Structures

IV - Control Flow

  1. Conditional Statements
  2. Loops
  3. Loop Control Statements
  4. Comprehensions

V - Functions and Modularization

  1. Defining Functions
  2. Returning Values
  3. Function Arguments
  4. Lambda Functions
  5. Recursive Functions
  6. Nested Functions
  7. Variable Scope
  8. Importing Functions and Modules

VI - File Handling

VII - Best Practices

  1. Choosing the Right Execution Mode
  2. Handling Command-Line Arguments with argparse
  3. Code Style and Readability
  4. Naming Conventions
  5. Commenting and Documentation
  6. Efficient Code Writing

I - Introduction to Python

1. What is Python?

Python is a high-level, interpreted programming language that is known for its simplicity and readability. It is widely used for a variety of tasks, including web development, data science, artificial intelligence, automation, and scripting. Python is popular among both beginners and experienced developers due to its clear syntax and large ecosystem of libraries and frameworks.

Key Characteristics:

  • High-level: Python allows developers to write code that is closer to human language than machine language, which makes it easier to understand and use. The language handles many of the complexities of low-level operations, like memory allocation, so developers can focus more on solving problems rather than managing hardware resources.
  • Interpreted: Python code is executed line-by-line by an interpreter, which makes it easy to test and debug.
  • Extensive Libraries: Python boasts a huge selection of built-in and third-party libraries for tasks like web scraping, data visualization, and machine learning.
  • Community Support: Python has an active and helpful community, making it easy to find tutorials, documentation, and solutions to problems.

2. Python Installation

Python can be easily installed on various operating systems. The Conda setup covered in the previous tutorial already includes Python as part of the installation. If you want to install Python manually, you can download it from the official website and follow the installation instructions based on your operating system.

If you're using Conda, Python will be pre-installed, and you can confirm this by running the following command in the terminal:

$ python --version

This will display the installed version of Python. You can also use python3 in some environments, especially if multiple versions of Python are installed.

3. Running Python Code

Python code can be executed in several ways:

  • Interactive Mode:

    You can run Python interactively directly from the terminal by simply typing python (or python3). This will launch the Python shell where you can execute code line by line.

    $ python
    >>> print("Hello, World!")
    Hello, World!
    >>> quit()  # Exits the interactive mode

    In this mode, you can quickly test small snippets of code.

  • Scripts:

    Python scripts are saved with the .py extension and can be run from the command line.

    $ python script.py

    You can add a "shebang" (#!/usr/bin/env python3) at the top of your Python file. This enables the script to be executed directly from the command line without needing to explicitly invoke the Python interpreter.

    $ ./script.py

    ⚠️ You may need to ensure the script has the correct permissions:

    $ chmod +x script.py
  • Notebooks:

    For a more interactive and flexible environment, Jupyter Notebooks allow you to run Python code in interactive cells, which can also contain code, text (written in Markdown), and rich media outputs (such as interactive plots or images).

    When executing Python code in a notebook, the cell will process the code and display the result directly below the input. Jupyter uses JavaScript to manage interactions between the notebook interface and Python, creating a hybrid environment ideal for experimentation, data analysis, and documentation.

II - Basic Syntax

1. Variables

Variables are used to store values that you can refer to later in your program. You can assign a value to a variable by using the = operator. A variable that has been assigned a value is considered declared.

age = 25

The variable age is now assigned the value 25.

name = "Alice"

The variable name is now assigned the value Alice.

⚠️ Here the quotes (") are necessary, otherwise python would think Alice is the name of another, previously-declared, variable from which we want to assign the value to age. Numbers (such as 25) cannot be used as variables, so they do not need to be surounded by quotes ".

You can assign a new value to a variable at any time:

age = 30

The variable age is now assigned the value 30.

Variable manipulation is at the core of programming, forming the foundation for more complex logic, functionality, and flexibility.

2. Comments

Comments are used to annotate your code with explanations or notes. They are ignored by the Python interpreter and are essential for making your code more understandable.

  • Single-line comments: You can add a comment by starting the line with a #.
# This is a comment
x = 10  # This is an inline comment
  • Multi-line comments: Although Python doesn't have a specific syntax for multi-line comments, you can use triple quotes (""" """ or ''' ''') to create block comments. These are often used for docstrings (documentation strings), but can also be used as comments.
"""
This is a multi-line comment.
It can span several lines.
"""
x = 20

3. Using Functions

Functions are a key part of programming that allow you to run specific tasks by calling pre-defined blocks of code. By using functions, you can simplify your code and perform advanced operations. Here are some basic functions:

  • The print() function is used to output information to the console. It is one of the most commonly used functions for displaying results or debugging information.

    print("Hello, world!")  # Output: Hello, world!
  • The input() function is used to capture input from the user.

    name = input("Enter your name: ")
    print("Hello,", name)

Functions in Python are called by using their name followed by parentheses (). Inside the parentheses, you can pass one or more parameters, separated by commas. These parameters are the inputs that the function uses to perform its task. Just like variables, functions are recognizable by their name, and the parentheses indicate that you're calling, or invoking, the function. For example, in print("Hello, world!"), print is the function, and "Hello, world!" is the parameter being passed to it.

Some functions will return a value after performing their task. This returned value can then be assigned to a variable for further use. The input() function, for instance, captures the user's input and returns it as a string, which can be stored in a variable.

4. Basic Data Types

In programming, variables have a data type that defines the kind of information they can store. Data types determine what operations can be applied to a variable and which functions can accept it. Conversely, most functions expect their parameters to have specific data types to work correctly. In Python, the data type of a variable is assigned implicitly and dynamically, meaning you don’t need to declare it explicitly: it’s automatically assigned based on the value the variable holds.

  • Here are some of Python's built-in data types:

    • int: Integer numbers, e.g., 5, -3, 42
    • float: Floating-point numbers, e.g., 3.14, -2.5, 0.99
    • bool: Boolean values, either True or False
    • str: Strings, or sequences of characters, e.g., "hello", "Alice", "42". Strings are always recognizable by the surrounding quotes (" or ').
    • None: A special type used to represent the absence of a value or a null value. It is often used to indicate that a variable has no value assigned or that a function does not return anything.
  • You can check the type of a variable using the type() function:

    x = 10
    print(type(x))  # Output: <class 'int'>
    
    y = 3.14
    print(type(y))  # Output: <class 'float'>
    
    z = "Hello"
    print(type(z))  # Output: <class 'str'>
    
    is_active = True
    print(type(is_active))  # Output: <class 'bool'>
    
    result = None
    print(type(result))  # Output: <class 'NoneType'>
  • You can also convert between types using functions like int(), float(), and str():

    x = "10"
    x = int(x)  # Convert string to integer
    
    y = 3
    y = str(y)  # Convert integer to string
    
    z = "3.14"
    z = float(z)  # Convert string to float

    ⚠️ However, the conversion (also named casting) needs to make sense and can sometimes be confusing, especially when working with booleans. For instance, when you cast a non-zero number to a boolean, it will be interpreted as True, and when you cast 0 or an empty string "", it will be interpreted as False:

    a = 5
    b = 0
    c = ""
    d = "Hello"
    
    print(bool(a))  # Output: True (since 5 is non-zero)
    print(bool(b))  # Output: False (since 0 is zero)
    print(bool(c))  # Output: False (since an empty string is considered False)
    print(bool(d))  # Output: True (non-empty string is considered True)

    This can be tricky at times, so it's important to ensure that the type conversions you make align with your intended behavior in the program.

5. Common Operators

  • Arithmetic operators:

    Python supports a wide variety of arithmetic operators for numerical calculations:

    • Addition (+): Adds two numbers.
    • Subtraction (-): Subtracts the second number from the first.
    • Multiplication (*): Multiplies two numbers.
    • Division (/): Divides the first number by the second, resulting in a float.
    • Exponentiation (**): Raises the first number to the power of the second.
    • Modulus (%): Returns the remainder of a division.
    • Floor Division (//): Divides the first number by the second and returns the largest integer less than or equal to the result.

    Here are some examples of using these operations:

    x = 10
    y = 3
    
    print(x + y)  # Output: 13
    
    print(x - y)  # Output: 7
    
    print(x * y)  # Output: 30
    
    print(x / y)  # Output: 3.333...
    
    print(x ** y)  # Output: 1000
    
    print(x % y)  # Output: 1
    
    print(x // y)  # Output: 3
  • Compound assignment operators

    Python also offers compound assignment operators, which allow you to perform arithmetic operations and assignments in one step. For example:

    • Addition Assignment (+=): Adds the right operand to the left operand and assigns the result to the left operand.
    • Subtraction Assignment (-=): Subtracts the right operand from the left operand and assigns the result to the left operand.
    • Multiplication Assignment (*=): Multiplies the left operand by the right operand and assigns the result to the left operand.
    • Division Assignment (/=): Divides the left operand by the right operand and assigns the result to the left operand.

    Some examples:

    x = 10
    y = 3
    
    x += y  # x = x + y, now x is 13
    print(x)  # Output: 13
    
    x *= 2  # x = x * 2, now x is 26
    print(x)  # Output: 26

    Compound assignment operators can make your code more concise and help you avoid repeating the variable name when performing operations.

  • Comparison operators:

    Comparison operators in Python are used to compare two values or variables. These operators return True or False based on the comparison results, and they are often used in conditional statements and loops.

    Here are the main comparison operators:

    • Equal to (==): Returns True if the two operands are equal.
    • Not equal to (!=): Returns True if the two operands are not equal.
    • Greater than (>): Returns True if the left operand is greater than the right operand.
    • Less than (<): Returns True if the left operand is less than the right operand.
    • Greater than or equal to (>=): Returns True if the left operand is greater than or equal to the right operand.
    • Less than or equal to (<=): Returns True if the left operand is less than or equal to the right operand.

    Examples:

    x = 10
    y = 5
    
    print(x == y)  # Output: False (x is not equal to y)
    print(x != y)  # Output: True (x is not equal to y)
    print(x > y)   # Output: True (x is greater than y)
    print(x < y)   # Output: False (x is not less than y)
    print(x >= y)  # Output: True (x is greater than or equal to y)
    print(x <= y)  # Output: False (x is not less than or equal to y)

    Comparison operators are essential in decision-making processes, helping you control the flow of your program based on conditions. They are commonly used in if statements, loops, and while checking the truth of expressions.

  • Logical operators:

    Python provides logical operators for combining boolean statements:

    • And (and): Returns True if both conditions are true.
    • Or (or): Returns True if at least one of the conditions is true.
    • Not (not): Reverses the truth value of the condition.

    Examples:

    x = 10
    y = 5
    
    print(x > 5 and y < 10)  # Output: True (both conditions are true)
    
    print(x > 5 or y > 10)  # Output: True (at least one condition is true)
    
    print(not(x > 5))  # Output: False (x > 5 is true, but "not" makes it false)

6. String Manipulation

Strings in Python are sequences of characters. You can manipulate them in various ways.

  • Indexing: You can access individual characters in a string by using square brackets ([]). Python uses zero-based indexing, meaning the first character is at index 0. You can also use negative indexing, which starts from the end of the string (with -1 being the last character).

    s = "Python"
    print(s[0])  # Output: P
    print(s[1])  # Output: y
    print(s[-1])  # Output: n (last character)
    print(s[-2])  # Output: o (second to last character)
  • Slicing: You can extract a portion of a string using the slicing syntax s[start:end], where start is the index of the first character, and end is the index of the character just past the last character you want.

    s = "Python"
    print(s[1:4])  # Output: yth (from index 1 to 3)
    print(s[-3:-1])  # Output: ho (from third to last character to the second to last character)
  • String Methods: Python provides a number of built-in methods to manipulate strings. Some common methods include:

    • .lower(): Converts all characters to lowercase.
    • .upper(): Converts all characters to uppercase.
    • .strip(): Removes whitespace from both ends of the string.
    • .replace(old, new): Replaces occurrences of a substring with a new string.
    s = " Hello, World! "
    
    print(s.lower())  # Output: hello, world!
    
    print(s.upper())  # Output: HELLO, WORLD!
    
    print(s.strip())  # Output: Hello, World!
    
    print(s.replace("Hello", "Hi"))  # Output: Hi, World!
  • String Formatting: Python allows you to format strings in a readable way using f-strings, the .format() method, or the % operator.

    name = "Alice"
    age = 30
    
    # Using f-strings
    print(f"My name is {name} and I am {age} years old.")
    
    # Using .format() method
    print("My name is {} and I am {} years old.".format(name, age))  
    
    # Using the `%` operator
    print("My name is %s and I am %d years old." % (name, age))
    
    # Outputs: My name is Alice and I am 30 years old.
  • Concatenation and Repetition: You can concatenate (combine) strings with the + operator and repeat them with the * operator.

    greeting = "Hello"
    name = "Alice"
    
    # Concatenate
    print(greeting + " " + name)  # Output: Hello Alice
    
    # Repeat
    print("Hi! " * 3)  # Output: Hi! Hi! Hi! 
  • Checking Substrings: You can check if a substring exists in a string using the in and not in operators.

    sentence = "Hello, world!"
    
    # Check if 'world' is in the sentence
    print("world" in sentence)  # Output: True
    
    # Check if 'goodbye' is not in the sentence
    print("goodbye" not in sentence)  # Output: True
  • Special Characters and Escape Sequences: Strings can contain special characters, which are often represented by escape sequences. An escape sequence starts with a backslash (\) and allows you to include characters that are difficult or impossible to type directly, like newline (\n), tab (\t), or a backslash itself (\\).

    # Newline
    print("Hello\nWorld!")  # Output: Hello (new line) World!
    
    # Tab
    print("Hello\tWorld!")  # Output: Hello    World!
    
    # Backslash
    print("C:\\Program Files\\Python")  # Output: C:\Program Files\Python

    Additionally, you can use escape sequences to include quotes within strings without ending the string (you can also use different types of quotes for the string and the quotes inside it).

    print("She said, \"Hello, World!\"")  # Output: She said, "Hello, World!"
    print('She said, "Hello, World!"')  # Output: She said, "Hello, World!"
    print('It\'s a sunny day!')  # Output: It's a sunny day!
    print("It's a sunny day!")  # Output: It's a sunny day!

⚠️ Note:

In Python, there are two common ways to use functions: method calls and function calls. Both involve calling functions, but they have different syntaxes and purposes.

  • Method call (var.function()): This is when you call a function that is associated with a particular variable or object. The function is tied to the object and is called using dot notation. The variable (var) is the object that the method operates on. For example, strings have built-in methods like .lower() or .upper(), which can be used to manipulate the string directly.

  • Function call (function(var)): This is when you call a standalone function and pass variables or values to it as parameters. The function is independent of any particular variable or object, and it operates on the arguments you provide.

    text = "Hello"
    print(text.lower())  # Output: hello

    In this case, lower() is a method of the text string object, and it operates specifically on that object. On the other hand, print() is a standalone function and isn't tied to a specific object.

III - Data Structures

In Python, data structures are special data types that allow us to store, organize, and manipulate collections of values efficiently. They help in structuring our programs and optimizing operations like searching, sorting, and accessing elements. Python provides several built-in data structures, each suited for different use cases.

1. Lists

A list is a collection of items that can store multiple values in an ordered sequence. Lists are mutable, meaning their contents can be changed after creation.

  • Creating a List

    Lists are defined using square brackets [], with elements separated by commas.

    # Creating a list of numbers
    numbers = [1, 2, 3, 4, 5]
    
    # Creating a mixed-type list - ⚠️Not advised
    mixed = [1, "hello", 3.5, True]
  • Accessing Elements

    Elements in a list can be accessed using zero-based indexing:

    numbers = [10, 20, 30, 40]
    
    print(numbers[0])  # First element: 10
    print(numbers[2])  # Third element: 30
  • Slicing Lists

    Slicing allows extracting a subset of a list using the [start:stop:step] notation:

    numbers = [0, 1, 2, 3, 4, 5, 6]
    
    print(numbers[1:4])   # [1, 2, 3] (from index 1 to 3)
    print(numbers[:3])    # [0, 1, 2] (first three elements)
    print(numbers[3:])    # [3, 4, 5, 6] (from index 3 to the end)
    print(numbers[::2])   # [0, 2, 4, 6] (every second element)
    print(numbers[::-1])  # [6, 5, 4, 3, 2, 1, 0] (reverse the list)
  • Modifying a List

    Lists support modification of elements:

    numbers[1] = 25  # Changing the second element
    print(numbers)  # [10, 25, 30, 40]
  • Adding and Removing Elements

    • Appending elements to the end of a list:

      numbers.append(50)  # [10, 25, 30, 40, 50]
    • Inserting elements at a specific index:

      numbers.insert(2, 15)  # Insert 15 at index 2
    • Removing elements:

      numbers.remove(30)  # Removes the first occurrence of 30
      last_item = numbers.pop()  # Removes and returns the last element

2. Tuples

A tuple is an immutable sequence of elements, meaning it cannot be modified after creation. Tuples are useful when you want to store a fixed collection of items.

  • Creating a Tuple

    Tuples use parentheses () or can be created without brackets:

    coordinates = (10.5, 20.3)
    single_element = (42,)  # Note the comma is necessary for single-element tuples
  • Accessing Tuple Elements

    Like lists, elements in a tuple can be accessed via indexing and slicing:

    tuple_data = (10, 20, 30, 40, 50)
    print(tuple_data[0])  # Output: 10
    print(tuple_data[1:4])  # Output: (20, 30, 40)
    print(tuple_data[::-1])  # Output: (50, 40, 30, 20, 10)

⚠️ Note:

Python data types can be categorized based on whether they allow modifications after creation:

  • Mutable types: Can be modified (e.g., lists, dictionaries, sets)
  • Immutable types: Cannot be modified (e.g., tuples, strings, numbers)

Example:

name = "Alice"
# name[0] = "B"  # This will raise an error because strings are immutable

numbers = [1, 2, 3]
numbers[0] = 10  # Allowed because lists are mutable

3. Dictionaries

A dictionary is an unordered collection of key-value pairs, where each key is unique (values can have duplicates). Dictionaries allow fast lookups and modifications.

  • Creating a Dictionary

    Dictionaries are created using curly braces {}:

    person = {
        "name": "Alice",
        "age": 25,
        "city": "New York"
    }
  • Accessing Values

    Values are retrieved using keys:

    print(person["name"])  # Output: Alice
  • Adding and Modifying Key-Value Pairs

    person["age"] = 26  # Modify existing key
    person["job"] = "Engineer"  # Add a new key-value pair
  • Removing Elements

    del person["city"]  # Removes the key-value pair with key "city"
    age = person.pop("age")  # Removes and returns the value for "age"

4. Sets

A set is an unordered collection of unique elements. Sets are useful for eliminating duplicates and performing set operations.

  • Creating a Set

    fruits = {"apple", "banana", "cherry"}
  • Adding and Removing Elements

    fruits.add("orange")  # Add an element
    fruits.remove("banana")  # Remove an element
  • Set Operations

    a = {1, 2, 3, 4}
    b = {3, 4, 5, 6}
    
    print(a | b)  # Union: {1, 2, 3, 4, 5, 6}
    print(a & b)  # Intersection: {3, 4}
    print(a - b)  # Difference: {1, 2}

5. Common Operators and Functions for Built-in Data Structures

Certain operations apply to most common built-in data structures in Python. Here's an overview:

  • Length (len()): Returns the number of elements in the structure.

    • Works for lists, strings, tuples, sets, and dictionaries.
    numbers = [1, 2, 3, 4]
    print(len(numbers))  # Output: 4
  • Membership (in): Checks if an element exists within the structure and returns a boolean.

    • Works for lists, strings, tuples, sets, and dictionaries (checks keys).
    numbers = [1, 2, 3, 4]
    print(2 in numbers)  # Output: True
    print(5 in numbers)  # Output: False
  • Equality (==): Checks if two structures have the same elements in the same order. Use != for inequality.

    • Works for lists, strings, tuples, sets, and dictionaries.
    numbers = [1, 2, 3, 4]
    print(numbers == [1, 2, 3, 4])  # Output: True
    print(numbers == [1, 2, 3])     # Output: False
    print(numbers != [1, 2, 3, 5])  # Output: True
  • String Joining (str.join()): This method takes an iterable (like a list) and joins its elements into a single string, using the string it is called on as a separator (delimiter) between elements.

    • Works for lists and other iterable sequences containing strings.
    words = ['Python', 'is', 'great']
    sentence = ' '.join(words)
    print(sentence)  # Output: Python is great
    
    hyphenated = '-'.join(words)
    print(hyphenated)  # Output: Python-is-great

Another operation that applies to all data structures is the iteration (using the for statement), which we’ll cover in the next section on Control Flow.

IV - Control Flow

Control flow determines the order in which statements are executed in a program. Python provides structures like conditional statements and loops to control execution based on conditions and repetitions.

1. Conditional Statements

Conditional statements are used to execute specific blocks of code based on certain conditions. Python uses if, elif, and else to define these conditions.

  • if Statement: Executes a block of code if a condition is True.

    age = 18
    if age >= 18:
        print("You are an adult.")
  • if-else Statement: Provides an alternative action if the condition is False.

    age = 16
    if age >= 18:
        print("You are an adult.")
    else:
        print("You are a minor.")
  • if-elif-else Statement: Checks multiple conditions in sequence.

    score = 85
    if score >= 90:
        print("Grade: A")
    elif score >= 80:
        print("Grade: B")
    elif score >= 70:
        print("Grade: C")
    else:
        print("Grade: F")

⚠️ Warning: Indentation is crucial in Python. Blocks inside if, elif, and else must be indented consistently.

  • Nest conditional statements: You can also nest conditional statements by adding further indented blocks inside existing blocks. This allows you to create more complex decision trees.

    age = 20
    if age >= 18:
        if age >= 21:
            print("You are allowed to drink alcohol.")
        else:
            print("You are an adult, but not allowed to drink alcohol.")
    else:
        print("You are a minor.")
  • match-case Statement: Introduced in Python 3.10, this allows for pattern matching. It's similar to switch statements found in other languages and is useful for matching complex data structures or multiple possible conditions.

    day = "Monday"
    match day:
        case "Monday":
            print("Start of the week!")
        case "Friday":
            print("Almost weekend!")
        case _:
            print("Mid-week day.")

    ⚠️ Tip: If you're using an older version of Python, you can use traditional if-elif-else statements instead of match-case, as the latter was introduced in Python 3.10 and is not available in earlier versions.

2. Loops

Loops allow us to execute a same block of code multiple times.

  • for loops

    A for loop iterates over a sequence (like a list, string, or range) and executes a block of code for each element.

    fruits = ["apple", "banana", "cherry"]
    for fruit in fruits:
        print(fruit)

    Looping with range():

    for i in range(5):
        print(i)  # Outputs 0, 1, 2, 3, 4
  • while loops

    A while loop runs as long as a condition remains True.

    count = 0
    while count < 3:
        print("Count:", count)
        count += 1

    ⚠️ Beware of infinite loops: Ensure while loops have an exit condition to prevent infinite execution.

3. Loop Control Statements

Loop control statements modify the flow inside loops:

  • break: Exits the loop immediately.

    for num in range(10):
        if num == 5:
            break
        print(num)  # Stops at 4
  • continue: Skips the current iteration and proceeds to the next.

    for num in range(5):
        if num == 2:
            continue
        print(num)  # Skips 2
  • pass: A placeholder when no action is required.

    for num in range(3):
        if num == 1:
            pass  # Does nothing
        else:
            print(num)

4. Comprehensions

Comprehensions provide a concise and readable way to create new data structures through iteration. Python supports comprehensions for various data types:

  • List Comprehension:
    Create lists from an iterable in a single line. You can also add conditions to filter elements.

    squares = [x ** 2 for x in range(5)]  # Output: [0, 1, 4, 9, 16]
    even_numbers = [x for x in range(10) if x % 2 == 0]  # Output: [0, 2, 4, 6, 8]
  • Dictionary Comprehension:
    Create dictionaries with key-value pairs from an iterable.

    square_dict = {x: x ** 2 for x in range(5)}  # Output: {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
  • Set Comprehension:
    Create sets from an iterable, ensuring uniqueness.

    unique_squares = {x ** 2 for x in range(5)}  # Output: {0, 1, 4, 9, 16}
  • Generator Comprehension (More advanced): Similar to list comprehensions but returns a generator object for memory efficiency.

    squares_gen = (x ** 2 for x in range(5))

V - Functions and Modularization

Functions are a fundamental part of Python programming, allowing you to write reusable blocks of code that can be called multiple times. In the previous sections, you encountered and used built-in functions (such as print(), input(), and len()). Now, we will explore how you can define our own functions and structure your code more effectively.

1. Defining Functions

In Python, we define functions using the def keyword, followed by the function name and parentheses (). The function body must be indented.

Here's a basic function definition and usage:

# Defining a function
def greet():
    print("Hello, world!")

# Calling the function
greet()  # Output: Hello, world!

2. Returning Values

A function can return a value using the return statement. This allows functions to pass data back to the caller.

def add(a, b):
    return a + b

result = add(3, 5)
print(result)  # Output: 8

A function without a return statement returns None by default.

3. Function Arguments

Functions can take parameters to accept input values. Python supports different types of arguments.

def greet(name, message):
    print(f"{message}, {name}!")

Positional vs Keyword Arguments

  • Positional Arguments: Values are assigned based on the order in which they are passed.
  • Keyword Arguments: Values are assigned using parameter names, making the order flexible.
# Positional Arguments
greet("Alice", "Hello")   # Output: Hello, Alice!

# Keyword Arguments
greet(message="Hi", name="Bob")   # Output: Hi, Bob

Default Values

You can assign default values to parameters. If an argument is not provided, the default value is used.

def greet(name, message="Hello"):
    print(f"{message}, {name}!")

greet("Alice")       # Output: Hello, Alice!
greet("Bob", "Hi")   # Output: Hi, Bob!

4. Lambda Functions

A lambda function is a small anonymous function defined using the lambda keyword. It can have multiple parameters but only one expression.

# Lambda function to add two numbers
add = lambda x, y: x + y
print(add(3, 5))  # Output: 8

Lambda functions are useful when you need a short function for immediate use, such as with map(), filter(), and sorted().

numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x ** 2, numbers))
print(squared)  # Output: [1, 4, 9, 16, 25]

5. Recursive Functions

A recursive function is a function that calls itself in order to solve a problem. These functions are useful for problems that can be broken down into smaller instances of the same problem, such as computing factorials or Fibonacci sequences.

def factorial(n):
    if n == 0:
        return 1
    return n * factorial(n - 1)

print(factorial(5))  # Output: 120

⚠️ Recursive functions must have a base case to avoid infinite recursion.

6. Nested Functions

In Python, you can define functions inside other functions. These are called nested functions. Nested functions are useful for encapsulating logic that is only relevant within the scope of the outer function. They help in keeping code modular and clean by limiting the scope of helper functions.

def outer_function():
    def inner_function():
        print("This is an inner function")
    inner_function()

outer_function()  # Output: This is an inner function

7. Variable Scope

In Python, variables have different scopes (or visibilities) that determine where they can be accessed. There are two primary types of variable scope: local and global.

  • Local variables are defined inside a function and can only be accessed within that function.
  • Global variables are defined outside any function and can be accessed anywhere in the script.

Example:

global_var = "I am global"

def example():
    local_var = "I am local"
    print(local_var)  # Accessible inside function
    print(global_var)  # Accessible inside function

example()
print(local_var)  # Error: local_var is not accessible here!

If you want to modify a global variable inside a function, you must use the global keyword:

global_count = 0

def increment():
    global global_count
    global_count += 1

increment()
print(global_count)  # Output: 1

8. Importing Functions and Modules

Python promotes code reusability through modules and packages—collections of functions, classes, and variables. You can import modules or specific functions from them to organize and reuse code effectively.

  • Importing an Entire Module

    You can import an entire module and access its functions using module.function().

    import math
    print(math.sqrt(16))  # Output: 4.0
  • Importing Specific Functions

    You can also import specific functions from a module to use them directly.

    from math import sqrt
    print(sqrt(25))  # Output: 5.0
  • Renaming Modules or Functions

    To avoid name conflicts or shorten long names, you can rename a module or function during import using the as keyword.

    import math as m
    print(m.pi)  # Output: 3.141592653589793
  • Creating Your Own Modules

    You can create your own Python module by writing functions in a .py file and importing it into other scripts.

    Example: Create a file my_module.py:

    # my_module.py
    def greet(name):
        return f"Hello, {name}!"

    Then, in another script, import and use the module:

    import my_module
    print(my_module.greet("Alice"))  # Output: Hello, Alice!
  • Using Packages

    A package is a collection of modules, typically organized in directories. You can import specific modules from a package. For example, Python’s built-in datetime package provides various modules for working with dates and times. Here’s how you can use it:

    import datetime
    current_time = datetime.datetime.now()
    print(current_time)  # Output: Current date and time

    If you want to import specific submodules from a package, you can do so like this:

    from datetime import datetime
    current_time = datetime.now()
    print(current_time)  # Output: Current date and time

    This allows you to efficiently work with different components of a package, depending on your needs.

Using modules, packages, and functions in your code enhances organization, readability, and reusability, making your projects easier to maintain and scale.

VI - File Handling

File handling is an essential part of programming that allows us to read from and write to external files. Python provides built-in functions to work with files efficiently.

In Python, we use the open() function to work with files. The open() function can return a file object, which allows us to read from or write to the file. When opening a file, we must specify a file mode:

  • r: Read mode (default). Opens the file for reading. If the file does not exist, it raises an error.
  • w: Write mode. Creates a new file if it does not exist or overwrites an existing file.
  • a: Append mode. Opens the file for writing but does not overwrite existing content; instead, it adds to the end of the file.
  • rb, wb, ab: Same as r, w, and a, but for binary files.

Using the with Statement

The with statement ensures proper resource management by automatically closing the file after reading or writing, which is important for preventing file corruption or memory leaks.

  • Reading Files

    # Read entire file
    with open("example.txt", "r") as file:
        content = file.read()
        print(content)
    
    # Read file line by line
    with open("example.txt", "r") as file:
        lines = file.readlines()
        for line in lines:
            print(line.strip())  # Removes extra newlines
  • Writing to Files

    # Write content to file (overwrites)
    with open("example.txt", "w") as file:
        file.write("Hello, Python!\n")
    
    # Append content to file
    with open("example.txt", "a") as file:
        file.write("This is an appended line.\n")

VII - Best Practices

Writing clean, efficient, and readable code is essential for becoming a proficient Python developer. Following best practices ensures maintainability, collaboration, and scalability. This section covers key principles to improve your coding skills.


1. Choosing the Right Execution Mode

Python can be run in different modes, each suitable for specific tasks:

  • Script Mode: Best for standalone applications, automation, or production-level code. You execute a Python file directly as an independent program.
  • Interactive Mode: Ideal for quick testing, debugging, or trying out small code snippets in the Python shell (REPL).
  • Jupyter Notebooks: Perfect for data analysis, visualization, and documentation, offering an interactive environment with inline outputs and plots.

Best Practice:
Use if __name__ == "__main__" to structure your code so that certain parts only run when the script is executed directly (not when it is imported as a module). This ensures flexibility and modularity.

2. Handling Command-Line Arguments with argparse

The argparse module is designed to simplify the process of handling command-line arguments. It allows you to build user-friendly command-line interfaces by defining required and optional arguments, performing type validation, and generating automatic help messages and error handling.

  • Basic Example:
import argparse

# Set up the argument parser
parser = argparse.ArgumentParser(description="Greet the user by name.")
parser.add_argument("name", type=str, help="Your name")

# Parse the command-line arguments
args = parser.parse_args()

# Output the greeting message
print(f"Hello, {args.name}!")

How it works:

  • ArgumentParser: Initializes the parser that will handle the arguments.
  • add_argument: Adds a specific argument, in this case, name, which is required and expects a string value.
  • parse_args: Parses the command-line input and stores the result in args, making it accessible in your script.

To run the script from the command line, simply pass the argument like so:

python script.py Alice

Output:

Hello, Alice!

This structure enables your scripts to interact with the user via command-line input, making them more flexible and scalable for various use cases.

3. Code Style and Readability

Following a consistent style improves code readability and maintainability.

  • Follow PEP 8 (Python Enhancement Proposal 8)

    • Use 4 spaces per indentation level.
    • Limit line length to 79 characters.
    • Add blank lines to separate logical sections.
    • Avoid unnecessary whitespace inside parentheses, brackets, and braces.
    • Use is and is not instead of == and != for comparing with None.
  • Use Meaningful Variable Names

    • Names should be descriptive and clear.
    • Example of a bad variable name:
      a = 25  # What does 'a' represent?
    • Example of a good variable name:
      age_of_user = 25
  • Keep Functions Short and Focused

    • A function should ideally perform a single task.
    • If a function becomes too long, consider breaking it into smaller functions.

4. Naming Conventions

Consistent naming makes code more readable and maintainable.

  • Variables and Functions: Use snake_case (lowercase with underscores).

    user_name = "Alice"
    def calculate_average(scores):
        return sum(scores) / len(scores)
  • Constants: Use UPPER_CASE.

    MAX_CONNECTIONS = 100
  • Class Names: Use PascalCase (also called CamelCase).

    class DataProcessor:
        pass

5. Commenting and Documentation

Well-documented code helps others (and your future self) understand your intentions.

  • Write Clear and Concise Comments

    • Explain why something is done rather than what it does.
    • Avoid redundant comments:
      x = x + 1  # Increments x by 1 (redundant)
  • Use Docstrings for Functions and Modules

    • A docstring (""" ... """) is a string at the beginning of a function, class, or module that describes its purpose.
    def add_numbers(a, b):
        """Returns the sum of two numbers."""
        return a + b

6. Efficient Code Writing

Writing efficient code helps optimize performance and maintainability.

  • Avoid Redundant Code

    • Use loops, functions, and list comprehensions to avoid repetition.
    # Instead of this:
    numbers = [1, 2, 3, 4]
    squares = []
    for num in numbers:
        squares.append(num ** 2)
    
    # Use list comprehension:
    squares = [num ** 2 for num in numbers]
  • Write Modular Code

    • Break down your code into reusable functions and modules.
    def greet_user(name):
        return f"Hello, {name}!"
    
    print(greet_user("Alice"))
  • Avoid Global Variables

    • Global variables can lead to unexpected side effects.
    • Use function parameters and return values instead.
    # Instead of modifying a global variable:
    total = 0
    
    def add_to_total(value):
        global total
        total += value
    
    # Pass the value instead:
    def calculate_total(total, value):
        return total + value
  • Use Built-in Functions Whenever Possible

    • Python provides many built-in functions that are optimized for performance.
    # Instead of manually summing a list:
    total = 0
    for num in [1, 2, 3, 4]:
        total += num
    
    # Use sum():
    total = sum([1, 2, 3, 4])

Following these best practices should help you write cleaner, more efficient, and maintainable Python code.