3. Advanced strings and functions, files and debugging.#

from math import pi 
import os 

3.1 Advanced Strings#

In Python, strings are created using quotes (’’ or “”) and are immutable, while f-strings are formatted strings that allow embedding expressions inside curly braces { } for dynamic value substitution during runtime. Here is a couple of examples for strings:

name = "Alice"
age = 25
profession = "engineer"

print (name,age,profession)
Alice 25 engineer

Here is the same example made as a complete sentence using f-strings.

intro = f"My name is {name}, I'm {age} years old, and I work as an {profession}."

print (intro)
My name is Alice, I'm 25 years old, and I work as an engineer.

Formatting numbers#

Python’s f-strings provide a convenient way to format numbers by using the colon character and specifying the desired format. The following table demonstrates a couple of examples with the number \(1\) using f-strings:

Code

Result

1:.2f

1.00

1:.0f

1

1:.10f

1.0000000000

1:%

100.000000%

1:.1%

100.0%

1:e

1.000000e+00

In the example below, the sleep() function from the time module is used to simulate the passage of time and provide a simple demonstration of a progress bar implementation. We define a function simulate_long_running_algorithm() that performs a loop with a sleep of 0.5 seconds between iterations. Within each iteration, the progress of the algorithm is calculated and used to construct a progress bar string. The progress bar consists of a series of equal signs (=) that visually represent the progress, followed by the percentage completion formatted to one decimal defined inside an f-strings.

import time

def simulate_long_running_algorithm():
    total_iterations = 10
    for i in range(total_iterations):
        time.sleep(0.5)  # Simulating processing time
        progress = (i + 1) / total_iterations
        progress_bar = f"[{'=' * int(progress * 20):20s}] {progress * 100:.1f}%"
        print(progress_bar, end='\r')  # Print on the same line
    print("\nAlgorithm complete!")

simulate_long_running_algorithm()
[====================] 100.0%
Algorithm complete!

Escape characters

Escape characters in programming are special characters that are used to represent certain non-printable or special characters within strings. They are typically represented by a backslash (’ \ ‘) followed by a specific character or sequence. We used two escape characters in the previous example, can you identify them?

Code

Result

Description

'

represents the escape sequence for a single quote (‘).

\

\

represents the escape sequence for a backslash (’ \ ‘).

\n

new line

represents the escape sequence for a new line character, which moves the cursor to the beginning of the next line.

\r

carriage return

represents the escape sequence for a carriage return character, which moves the cursor to the beginning of the current line.

\t

tab

represents the escape sequence for a tab character, which adds horizontal spacing.

\b

backspace

represents the escape sequence for a backspace character, which moves the cursor one position back.

3.2 Advanced Functions#

An example of an advanced fuction can be the lambda function. It is an anonymous function in Python that can be defined in a single line using the lambda keyword. It is typically used for simple and concise operations without the need for a formal function definition.

Here’s an example that showcases the difference between lambda functions and normal functions:

# Normal function
def multiply(x, y):
    return x * y

result = multiply(3, 4)
print(result)  # Output: 12

# Lambda function
multiply_lambda = lambda x, y: x * y

result_lambda = multiply_lambda(3, 4)
print(result_lambda)  # Output: 12
12
12

Both the normal function and the lambda function are used to multiply 3 and 4. The results obtained from both approaches are identical (12). The key difference is that the normal function is defined with the def keyword, whereas the lambda function is defined using the lambda keyword without a formal function name.

lambda functions are particularly useful in scenarios where a small, one-time function is needed without the need for a full function definition and name.

3.3 Working with files#

A lot of the work you’ll do in Python will have the following structure:

  1. Read data from a file

  2. Perform computations on the data

  3. Visualize the results and/or save the results to a file

File paths#

File paths on computers work based on a hierarchical structure, often represented as a tree. The root directory serves as the starting point, typically represented by a drive letter (e.g., C: on Windows). From the root directory, you can navigate to other directories using a delimiter character (\ on Windows or / on Unix-based systems). Each directory can contain files and subdirectories, forming a hierarchical structure.

Absolute paths specify the complete path from the root directory, while relative paths are relative to the current working directory. By understanding and manipulating file paths, you can effectively locate and access files and directories on a computer’s file system.

pathlib and os modules#

The os module in Python provides functions for interacting with the operating system, offering operations related to file management, directory handling, process management, and environment variables. It allows you to perform tasks such as creating, deleting, and modifying files and directories, launching external processes, accessing and modifying environment variables, and writing platform-independent code.

On the other hand, the pathlib module introduced in Python 3.4 offers an object-oriented approach to working with file paths and directories. It provides the Path class, which represents paths as objects, allowing for more intuitive and expressive manipulation of paths compared to the traditional string-based operations in os. With pathlib, you can perform operations like joining paths, checking file existence, accessing file attributes, and creating directories in a more convenient and readable manner.

The table below summuraizes some codes you can use for creating and adjusting your own file paths:

Code

Result

Description

os.path.join(path, *paths)

Path string

Joins one or more path components intelligently. It concatenates the arguments using the appropriate path delimiter for the operating system.

os.path.abspath(path)

Absolute path string

Returns the absolute path of the specified path. It resolves any symbolic links and references to parent directories.

os.path.exists(path)

Boolean

Checks if the specified path exists in the file system. Returns True if the path exists, and False otherwise.

os.path.isdir(path)

Boolean

Checks if the specified path is a directory. Returns True if the path is a directory, and False otherwise.

os.path.isfile(path)

Boolean

Checks if the specified path is a regular file. Returns True if the path is a file, and False otherwise.

os.path.splitext(path)

Tuple (base, ext)

Splits the specified path into its base name and extension. Returns a tuple where the first element is the base name and the second element is the extension (including the dot).

os.path.basename(path)

Base name string

Returns the base name (the file or directory name) from the specified path.

os.path.dirname(path)

Directory name string

Returns the directory name from the specified path.

Here are some examples of how to use these codes:

import os

path = os.path.join('folder', 'subfolder', 'file.txt')  # Joining path components intelligently using appropriate delimiter.
absolute_path = os.path.abspath(path)  # Getting the absolute path of the specified path.
exists = os.path.exists(path)  # Checking if the specified path exists.
is_directory = os.path.isdir(path)  # Checking if the specified path is a directory.
is_file = os.path.isfile(path)  # Checking if the specified path is a file.
base_name, extension = os.path.splitext(path)  # Splitting the path into base name and extension.
basename = os.path.basename(path)  # Getting the base name (file or directory name) from the path.
dirname = os.path.dirname(path)  # Getting the directory name from the path.

# Printing the information
print("Path:", path)  # Path string
print("Absolute Path:", absolute_path)  # Absolute path string
print("Exists:", exists)  # Boolean indicating if path exists
print("Is Directory:", is_directory)  # Boolean indicating if path is a directory
print("Is File:", is_file)  # Boolean indicating if path is a file
print("Base Name:", basename)  # Base name of the file or directory
print("Directory Name:", dirname)  # Directory name of the path
print("Extension:", extension)  # File extension with the dot
Path: folder\subfolder\file.txt
Absolute Path: c:\Users\ahmed\Documents\GitHub\learn-python\book\03\In_a_Nutshell\folder\subfolder\file.txt
Exists: False
Is Directory: False
Is File: False
Base Name: file.txt
Directory Name: folder\subfolder
Extension: .txt

This code demonstrates the usage of various os.path functions to perform operations on file paths, such as joining paths, obtaining absolute paths, checking existence, identifying directories or files, splitting paths into base names and extensions, and retrieving the base name and directory name from a path. The corresponding outputs are displayed to provide the relevant information.

3.4 Debugging#

Debugging in Python refers to the process of identifying and resolving errors or issues in a program. It involves analyzing the code execution, tracking down bugs, and correcting them to ensure the program functions as intended.

Python provides several built-in tools and techniques for debugging, including print statements, using a debugger, logging, and exception handling. Debugging allows developers to gain insights into the program’s flow, variable values, and identify the root cause of errors, ultimately improving the program’s reliability and performance.

1. Syntax errors#

Syntax errors in Python are mistakes in the structure or grammar of the code that violate the language’s syntax rules, resulting in the program failing to execute.

Example with a syntax error:

print("Hello, World!"
  Cell In[9], line 1
    print("Hello, World!"
                         ^
SyntaxError: incomplete input

Could you spot and fix the syntax error? The error was missing to close the parenthesis,the solution is shown below:

print("Hello, World!")

2. Runtime errors#

Runtime errors, also known as exceptions, occur during the execution of a program when unexpected conditions or behaviors are encountered, leading to program termination or abnormal behavior.

For example:

# Runtime error: accessing an index that is out of range
numbers = [1, 2, 3]
print(numbers[3])
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[16], line 3
      1 # Runtime error: accessing an index that is out of range
      2 numbers = [1, 2, 3]
----> 3 print(numbers[3])

IndexError: list index out of range

Solution: Use a valid index within the range of the list. See the cell below:

numbers = [1, 2, 3]
print(numbers[2])
3

3. Semantic errors#

Semantic errors, also known as logic errors, are mistakes in the program’s logic or algorithm that lead to undesired or incorrect behavior without causing the program to terminate or throw an error.

For example:

# Semantic error: incorrect calculation of the average
numbers = [5, 8, 12, 3, 6]
total = sum(numbers)
average = total / len(numbers) + 1
print (average)
7.8

Solution: Move the addition after calculating the average. Like this:

real_average = (total + 1) / len(numbers)
print (real_average)
7.0

Debugging strategies#

Debugging strategies are a set of techniques and approaches used to identify and fix errors or issues in code during the software development process, ensuring the program functions correctly and as intended. Some debugging strategies that can be applied by you during your work could include:

  1. Using print statements: Inserting print statements at critical points in the code to display the values of variables and track the program’s flow.

  2. Utilizing a debugger: Running the code in a debugger, setting breakpoints, and stepping through the code line by line to examine variable values and identify issues.

  3. Logging: Adding log statements to record the program’s execution and capture relevant information for analysis.

  4. Exception handling: Wrapping sections of code in try-except blocks to catch and handle specific exceptions, allowing for graceful error handling and troubleshooting.

  5. Code review: Having someone else review the code to spot potential errors or provide fresh insights.

By employing these strategies, you can effectively identify and resolve issues in your code, enhancing program functionality and reliability.