Course Topics
Introduction Python Overview Setting Up Python Python Syntax Basics First Steps with Python Comparing Python with Other Languages Basics Variables and Data Types Input and Output Type Conversion Comments and Code Readability Naming Conventions Control Flow Conditional Statements Loops in Python Loop Control Mechanisms Nested Control Structures Data Structures Working with Strings Lists Tuples Sets Dictionaries Comprehensions Iterators and Generators Functions Defining and Calling Functions Function Arguments and Parameters Lambda Functions Return Values Recursion Variable Scope in Functions Modules and Packages Importing Modules Built-in Modules Creating Custom Modules Working with Packages Virtual Environments Managing Packages with pip Object-Oriented Programming Classes and Objects Attributes and Methods Constructors and Initializers Inheritance and Polymorphism Encapsulation and Abstraction Class Methods and Static Methods Using super() and Method Resolution File Handling Reading and Writing Text Files File Modes and File Pointers Using Context Managers (with) Working with CSV Files Handling JSON Data Error Handling Types of Errors and Exceptions Try, Except, Finally Blocks Raising Exceptions Built-in vs Custom Exceptions Exception Handling Best Practices Advanced Python Decorators Advanced Generators Context Managers Functional Programming Tools Coroutines and Async Programming Introduction to Metaclasses Memory Management in Python Useful Libraries Math Module Random Module Date and Time Handling Regular Expressions (re) File and OS Operations (os, sys, shutil) Data Structures Enhancements (collections, itertools) Web APIs (requests) Data Analysis Libraries (NumPy, Pandas) Visualization Tools (matplotlib) Database Access SQLite in Python Connecting to MySQL/PostgreSQL Executing SQL Queries Using ORMs (SQLAlchemy Intro) Transactions and Error Handling Web Development Introduction to Web Frameworks Flask Basics (Routing, Templates) Django Overview Handling Forms and Requests Creating REST APIs Working with JSON and HTTP Methods Testing and Debugging Debugging Techniques Using assert and Logging Writing Unit Tests (unittest) Introduction to pytest Handling and Fixing Common Bugs Automation and Scripting Automating File Operations Web Scraping with BeautifulSoup Automating Excel Tasks (openpyxl) Sending Emails with Python Task Scheduling and Timers System Automation with subprocess

Working with Strings

What are Strings?

Strings are sequences of characters used to represent text in Python. Think of them as a chain of letters, numbers, symbols, and spaces that you can manipulate, search through, and transform in countless ways.

In Python, strings are immutable, meaning once created, they cannot be changed. However, you can create new strings based on existing ones.

Example:

greeting = "Hello, World!"
name = "Alice"
sentence = "Python programming is fun!"
number_as_text = "12345"

Python treats anything enclosed in quotes as a string, whether it contains letters, numbers, or symbols.


Creating Strings

Python offers multiple ways to create strings using different quote styles:

Single and Double Quotes

# Single quotes
message = 'Hello there!'
name = 'John'

# Double quotes  
description = "This is a Python string"
city = "New York"

# Both are equivalent - choose based on content
quote1 = "He said 'Hello' to me"      # Use double quotes for single quotes inside
quote2 = 'She replied "Hi there!"'    # Use single quotes for double quotes inside

Triple Quotes for Multi-line Strings

# Triple single quotes
paragraph = '''This is a multi-line string.
It can span several lines without using
escape characters or concatenation.
Perfect for documentation!'''

# Triple double quotes
email_template = """Dear {name},

Thank you for your purchase of {item}.
Your order will be shipped within 2-3 business days.

Best regards,
Customer Service Team"""

print(paragraph)
print(email_template.format(name="Sarah", item="Python Book"))

Raw Strings

Raw strings treat backslashes literally, useful for file paths and regular expressions:

# Regular string (backslashes are escape characters)
path1 = "C:\\Users\\John\\Documents\\file.txt"

# Raw string (backslashes are literal)
path2 = r"C:\Users\John\Documents\file.txt"

print(path1)  # C:\Users\John\Documents\file.txt
print(path2)  # C:\Users\John\Documents\file.txt

# Raw strings are especially useful for regex patterns
import re
pattern = r"\d+\.\d+"  # Matches decimal numbers like 3.14

String Operations

Concatenation (Joining Strings)

first_name = "Emma"
last_name = "Watson"

# Using + operator
full_name = first_name + " " + last_name
print(full_name)  # Emma Watson

# Using += for accumulation
message = "Welcome"
message += " to"
message += " Python!"
print(message)  # Welcome to Python!

# Multiple concatenation
greeting = "Hello" + " " + "beautiful" + " " + "world" + "!"
print(greeting)  # Hello beautiful world!

Repetition

# Using * operator
laugh = "Ha" * 5
print(laugh)  # HaHaHaHaHa

border = "=" * 50
print(border)  # ==================================================

# Creating patterns
pattern = "AB" * 10
print(pattern)  # ABABABABABABABABAB

# Useful for formatting
separator = "-" * 30
print(f"Title")
print(separator)
print("Content here")

String Length

text = "Python Programming"
length = len(text)
print(f"'{text}' has {length} characters")  # 'Python Programming' has 18 characters

# Length includes spaces and special characters
sentence = "Hello, World! 123"
print(f"Length: {len(sentence)}")  # Length: 15

# Empty string
empty = ""
print(f"Empty string length: {len(empty)}")  # Empty string length: 0

String Indexing and Slicing

Strings in Python are indexed, meaning each character has a position number starting from 0.

Indexing (Accessing Individual Characters)

word = "Python"
#       012345  (positive indices)
#      -654321  (negative indices)

# Positive indexing (left to right)
print(word[0])   # P (first character)
print(word[1])   # y (second character)
print(word[5])   # n (last character)

# Negative indexing (right to left)
print(word[-1])  # n (last character)
print(word[-2])  # o (second to last)
print(word[-6])  # P (first character)

# Index out of range causes error
# print(word[10])  # IndexError: string index out of range

Slicing (Extracting Substrings)

text = "Programming"
#       01234567890  (indices)

# Basic slicing: [start:end] (end is exclusive)
print(text[0:4])    # Prog (characters 0, 1, 2, 3)
print(text[2:7])    # ogram (characters 2, 3, 4, 5, 6)
print(text[4:11])   # ramming (characters 4 through 10)

# Omitting start or end
print(text[:4])     # Prog (from beginning to index 3)
print(text[4:])     # ramming (from index 4 to end)
print(text[:])      # Programming (entire string)

# Negative indices in slicing
print(text[-7:-2])  # gramm (from 7th last to 3rd last)
print(text[-4:])    # ming (last 4 characters)
print(text[:-3])    # Programm (all except last 3)

Advanced Slicing with Step

alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

# Step parameter: [start:end:step]
print(alphabet[::2])    # ACEGIKMOQSUWY (every 2nd character)
print(alphabet[1::2])   # BDFHJLNPRTVXZ (every 2nd, starting from index 1)
print(alphabet[::3])    # ADGJMPSVY (every 3rd character)

# Reversing strings with negative step
print(alphabet[::-1])   # ZYXWVUTSRQPONMLKJIHGFEDCBA (reverse)
print(alphabet[::-2])   # ZXVTRPNLJHFDB (reverse, every 2nd)

# Practical examples
text = "Hello World"
print(text[::-1])       # dlroW olleH (reverse)
print(text[::2])        # HloWrd (every other character)

String Methods

Python provides many built-in methods to manipulate and analyze strings:

Case Conversion Methods

text = "Hello Python World"

# Converting case
print(text.upper())      # HELLO PYTHON WORLD
print(text.lower())      # hello python world
print(text.capitalize()) # Hello python world (only first letter)
print(text.title())      # Hello Python World (title case)
print(text.swapcase())   # hELLO pYTHON wORLD (swap case)

# Checking case
mixed_text = "PyThOn"
print(mixed_text.isupper())    # False
print(mixed_text.islower())    # False
print("HELLO".isupper())       # True
print("hello".islower())       # True
print("Hello World".istitle()) # True

Whitespace Methods

messy_text = "   Hello Python World   "

# Removing whitespace
print(f"'{messy_text.strip()}'")   # 'Hello Python World'
print(f"'{messy_text.lstrip()}'")  # 'Hello Python World   '
print(f"'{messy_text.rstrip()}'")  # '   Hello Python World'

# Removing specific characters
text_with_chars = "...Hello World!!!"
print(text_with_chars.strip('.!'))  # Hello World

# Checking for whitespace
print("   ".isspace())   # True
print("Hello".isspace()) # False

Search and Check Methods

sentence = "Python is amazing and Python is powerful"

# Finding substrings
print(sentence.find("Python"))      # 0 (first occurrence)
print(sentence.find("amazing"))     # 10
print(sentence.find("Java"))        # -1 (not found)
print(sentence.rfind("Python"))     # 23 (last occurrence)

# Checking contents
print(sentence.startswith("Python"))   # True
print(sentence.endswith("powerful"))   # True
print("Python" in sentence)           # True
print("Java" not in sentence)         # True

# Counting occurrences
print(sentence.count("Python"))        # 2
print(sentence.count("is"))            # 2
print(sentence.count("amazing"))       # 1

Character Type Methods

# Testing string content
print("123".isdigit())        # True
print("12.3".isdigit())       # False
print("abc".isalpha())        # True
print("abc123".isalnum())     # True
print("Hello World".isalnum()) # False (contains space)

# Practical example: validating user input
user_id = "user123"
password = "Pass123!"

if user_id.isalnum():
    print("Valid user ID")
else:
    print("User ID must contain only letters and numbers")

if password.isalpha():
    print("Weak password (only letters)")
elif any(c.isdigit() for c in password):
    print("Password contains numbers - good!")

Split and Join Methods

# Splitting strings
sentence = "Python is a powerful programming language"
words = sentence.split()  # Split by whitespace (default)
print(words)  # ['Python', 'is', 'a', 'powerful', 'programming', 'language']

# Split by specific delimiter
data = "apple,banana,orange,grape"
fruits = data.split(",")
print(fruits)  # ['apple', 'banana', 'orange', 'grape']

# Split with maximum splits
text = "one-two-three-four-five"
parts = text.split("-", 2)  # Split only first 2 occurrences
print(parts)  # ['one', 'two', 'three-four-five']

# Joining strings
words = ["Python", "is", "awesome"]
sentence = " ".join(words)
print(sentence)  # Python is awesome

numbers = ["1", "2", "3", "4", "5"]
csv_data = ",".join(numbers)
print(csv_data)  # 1,2,3,4,5

Replace and Translate Methods

text = "I love Java programming. Java is great!"

# Simple replacement
new_text = text.replace("Java", "Python")
print(new_text)  # I love Python programming. Python is great!

# Limited replacements
limited_replace = text.replace("Java", "Python", 1)  # Replace only first occurrence
print(limited_replace)  # I love Python programming. Java is great!

# Multiple replacements using a loop
replacements = {"Java": "Python", "great": "amazing", "love": "enjoy"}
result = text
for old, new in replacements.items():
    result = result.replace(old, new)
print(result)  # I enjoy Python programming. Python is amazing!

String Formatting

Python offers several powerful ways to format strings with variables:

F-strings (Python 3.6+) - Recommended

name = "Alice"
age = 28
salary = 75000.50
pi = 3.14159265359

# Basic f-string formatting
message = f"Hello {name}, you are {age} years old."
print(message)  # Hello Alice, you are 28 years old.

# Expressions inside f-strings
print(f"{name} will be {age + 1} next year")
print(f"Doubled age: {age * 2}")
print(f"Name length: {len(name)}")

# Number formatting
print(f"Salary: ${salary:,.2f}")        # Salary: $75,000.50
print(f"Pi rounded: {pi:.2f}")          # Pi rounded: 3.14
print(f"Percentage: {0.875:.1%}")       # Percentage: 87.5%

# Alignment and padding
print(f"{'Name':<10} {'Age':>5} {'Salary':>10}")
print(f"{name:<10} {age:>5} {salary:>10,.0f}")
# Output:
# Name            Age     Salary
# Alice            28     75,001

.format() Method

# Basic formatting
template = "Hello {}, you are {} years old."
message = template.format("Bob", 25)
print(message)  # Hello Bob, you are 25 years old.

# Named placeholders
template = "Hello {name}, you are {age} years old and live in {city}."
message = template.format(name="Carol", age=30, city="Seattle")
print(message)  # Hello Carol, you are 30 years old and live in Seattle.

# Index-based formatting
template = "Items: {0}, {1}, {2}. First item again: {0}"
message = template.format("apple", "banana", "orange")
print(message)  # Items: apple, banana, orange. First item again: apple

# Number formatting with .format()
price = 1234.5678
print("Price: ${:.2f}".format(price))    # Price: $1234.57
print("Price: ${:,.2f}".format(price))   # Price: $1,234.57

% Formatting (Older Style)

name = "David"
score = 95
average = 87.5

# Basic % formatting
message = "Student %s scored %d%%" % (name, score)
print(message)  # Student David scored 95%

# Different format specifiers
print("Name: %s, Score: %d, Average: %.1f" % (name, score, average))
# Output: Name: David, Score: 95, Average: 87.5

# Dictionary-based formatting
data = {"name": "Eve", "score": 88}
message = "%(name)s scored %(score)d points" % data
print(message)  # Eve scored 88 points

Escape Characters

Escape characters allow you to include special characters in strings:

# Common escape characters
print("Line 1\nLine 2")          # \n = newline
print("Column1\tColumn2")        # \t = tab
print("She said \"Hello\"")      # \" = double quote
print('He replied \'Hi there\'') # \' = single quote
print("Path: C:\\Users\\John")   # \\ = backslash

# More escape characters
print("Ring the bell: \a")       # \a = bell/alert (may not work in all environments)
print("Backspace test: ABC\bD")  # \b = backspace
print("Form feed: \f")           # \f = form feed
print("Carriage return: ABC\rXYZ") # \r = carriage return

# Unicode characters
print("Greek letter pi: \u03C0")    # π
print("Smiley face: \U0001F600")    # 😀
print("Copyright symbol: \u00A9")   # ©

Raw Strings vs Escape Characters

# Regular string with escapes
path1 = "C:\\Users\\John\\Documents\\file.txt"
regex1 = "\\d+\\.\\d+"

# Raw strings (no escape processing)
path2 = r"C:\Users\John\Documents\file.txt"
regex2 = r"\d+\.\d+"

print(path1)   # C:\Users\John\Documents\file.txt
print(path2)   # C:\Users\John\Documents\file.txt
print(regex1)  # \d+\.\d+
print(regex2)  # \d+\.\d+

# When you need actual newlines in raw strings
multi_line = r"""Line 1
Line 2
Line 3"""
print(multi_line)

String Comparison

Python compares strings lexicographically (dictionary order):

# Basic comparison
print("apple" == "apple")    # True
print("apple" != "orange")   # True
print("apple" < "banana")    # True (lexicographic order)
print("Apple" < "apple")     # True (uppercase comes before lowercase)

# Case-insensitive comparison
name1 = "Alice"
name2 = "alice"
print(name1 == name2)                    # False
print(name1.lower() == name2.lower())    # True

# Comparing with numbers in strings
print("10" < "2")      # True (string comparison, not numeric)
print("10" < "9")      # True (compares first character '1' < '9')

# Practical example: sorting names
names = ["Alice", "bob", "Charlie", "david"]
names.sort()  # Case-sensitive sort
print(names)  # ['Alice', 'Charlie', 'bob', 'david']

names.sort(key=str.lower)  # Case-insensitive sort
print(names)  # ['Alice', 'bob', 'Charlie', 'david']

Working with String Collections

Processing Lists of Strings

# List of strings
fruits = ["apple", "banana", "cherry", "date", "elderberry"]

# Finding strings by length
short_fruits = [fruit for fruit in fruits if len(fruit) <= 5]
print(short_fruits)  # ['apple', 'date']

# Capitalizing all strings
capitalized = [fruit.capitalize() for fruit in fruits]
print(capitalized)  # ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry']

# Filtering strings
a_fruits = [fruit for fruit in fruits if fruit.startswith('a')]
print(a_fruits)  # ['apple']

# Getting string statistics
total_chars = sum(len(fruit) for fruit in fruits)
average_length = total_chars / len(fruits)
print(f"Average fruit name length: {average_length:.1f}")

Text Processing Example

# Processing a paragraph
text = """Python is a high-level programming language.
It emphasizes code readability and simplicity.
Python supports multiple programming paradigms."""

# Split into sentences
sentences = text.split('.')
sentences = [s.strip() for s in sentences if s.strip()]  # Remove empty strings

print("Sentences:")
for i, sentence in enumerate(sentences, 1):
    word_count = len(sentence.split())
    print(f"{i}. {sentence} ({word_count} words)")

# Word frequency analysis
all_words = text.lower().split()
word_count = {}
for word in all_words:
    # Remove punctuation
    clean_word = word.strip('.,!?;')
    word_count[clean_word] = word_count.get(clean_word, 0) + 1

print("\nWord frequencies:")
for word, count in sorted(word_count.items()):
    print(f"{word}: {count}")

Input Validation with Strings

Validating User Input

def get_valid_name():
    """Get a valid name from user input."""
    while True:
        name = input("Enter your name: ").strip()

        if not name:
            print("Name cannot be empty. Please try again.")
        elif not name.replace(" ", "").isalpha():
            print("Name should contain only letters and spaces. Please try again.")
        elif len(name) < 2:
            print("Name should be at least 2 characters long. Please try again.")
        else:
            return name.title()  # Return in title case

def get_valid_email():
    """Get a valid email address from user input."""
    while True:
        email = input("Enter your email: ").strip().lower()

        if not email:
            print("Email cannot be empty. Please try again.")
        elif "@" not in email or "." not in email:
            print("Please enter a valid email address. Please try again.")
        elif email.count("@") != 1:
            print("Email should contain exactly one @ symbol. Please try again.")
        else:
            return email

# Usage example
print("=== User Registration ===")
user_name = get_valid_name()
user_email = get_valid_email()

print(f"\nWelcome {user_name}!")
print(f"Confirmation email will be sent to {user_email}")

String Algorithms and Patterns

Palindrome Checker

def is_palindrome(text):
    """Check if a string is a palindrome (reads same forwards and backwards)."""
    # Clean the text: remove spaces and convert to lowercase
    cleaned = "".join(text.lower().split())

    # Remove non-alphabetic characters
    cleaned = "".join(char for char in cleaned if char.isalpha())

    # Check if cleaned string equals its reverse
    return cleaned == cleaned[::-1]

# Test palindromes
test_strings = [
    "racecar",
    "A man a plan a canal Panama",
    "race a car",
    "hello",
    "Madam",
    "Was it a rat I saw?"
]

for text in test_strings:
    result = "is" if is_palindrome(text) else "is not"
    print(f"'{text}' {result} a palindrome")

String Encryption (Caesar Cipher)

def caesar_cipher(text, shift):
    """Encrypt text using Caesar cipher with given shift."""
    result = ""

    for char in text:
        if char.isalpha():
            # Determine if uppercase or lowercase
            base = ord('A') if char.isupper() else ord('a')
            # Shift character and wrap around using modulo
            shifted = (ord(char) - base + shift) % 26
            result += chr(base + shifted)
        else:
            # Non-alphabetic characters remain unchanged
            result += char

    return result

def caesar_decrypt(text, shift):
    """Decrypt Caesar cipher by shifting in opposite direction."""
    return caesar_cipher(text, -shift)

# Example usage
message = "Hello World!"
encrypted = caesar_cipher(message, 3)
decrypted = caesar_decrypt(encrypted, 3)

print(f"Original:  {message}")
print(f"Encrypted: {encrypted}")
print(f"Decrypted: {decrypted}")

File and String Processing

Reading and Processing Text Files

def analyze_text_file(filename):
    """Analyze a text file and return statistics."""
    try:
        with open(filename, 'r', encoding='utf-8') as file:
            content = file.read()

        # Basic statistics
        lines = content.split('\n')
        words = content.split()
        characters = len(content)

        # Character frequency (letters only)
        char_freq = {}
        for char in content.lower():
            if char.isalpha():
                char_freq[char] = char_freq.get(char, 0) + 1

        # Most common words (simple approach)
        word_freq = {}
        for word in words:
            clean_word = word.lower().strip('.,!?;:"()[]{}')
            if clean_word and clean_word.isalpha():
                word_freq[clean_word] = word_freq.get(clean_word, 0) + 1

        return {
            'lines': len(lines),
            'words': len(words),
            'characters': characters,
            'unique_words': len(word_freq),
            'most_common_char': max(char_freq, key=char_freq.get) if char_freq else None,
            'most_common_word': max(word_freq, key=word_freq.get) if word_freq else None
        }

    except FileNotFoundError:
        return "File not found"
    except Exception as e:
        return f"Error reading file: {e}"

# Example usage (you would need an actual file)
# stats = analyze_text_file("sample.txt")
# print(f"File statistics: {stats}")

Practice Examples

Example 1: Text Formatter

def format_text_block(text, width=50, align='left'):
    """Format a block of text with specified width and alignment."""
    words = text.split()
    lines = []
    current_line = []
    current_length = 0

    for word in words:
        # Check if adding this word would exceed width
        if current_length + len(word) + len(current_line) > width:
            # Finish current line and start new one
            if current_line:
                line_text = " ".join(current_line)

                if align == 'center':
                    lines.append(line_text.center(width))
                elif align == 'right':
                    lines.append(line_text.rjust(width))
                else:  # left align
                    lines.append(line_text.ljust(width))

                current_line = []
                current_length = 0

        current_line.append(word)
        current_length += len(word)

    # Handle last line
    if current_line:
        line_text = " ".join(current_line)
        if align == 'center':
            lines.append(line_text.center(width))
        elif align == 'right':
            lines.append(line_text.rjust(width))
        else:
            lines.append(line_text.ljust(width))

    return "\n".join(lines)

# Example usage
sample_text = "Python is a powerful programming language that emphasizes code readability and simplicity. It supports multiple programming paradigms and has a large standard library."

print("Left aligned:")
print(format_text_block(sample_text, width=40, align='left'))
print("\nCenter aligned:")
print(format_text_block(sample_text, width=40, align='center'))
print("\nRight aligned:")
print(format_text_block(sample_text, width=40, align='right'))

Example 2: String Pattern Matcher

def find_patterns(text, pattern):
    """Find all occurrences of a pattern in text (case-insensitive)."""
    text_lower = text.lower()
    pattern_lower = pattern.lower()
    positions = []

    start = 0
    while True:
        pos = text_lower.find(pattern_lower, start)
        if pos == -1:
            break
        positions.append(pos)
        start = pos + 1

    return positions

def highlight_pattern(text, pattern, highlight_char='*'):
    """Highlight occurrences of pattern in text."""
    positions = find_patterns(text, pattern)
    if not positions:
        return text

    # Build result string with highlights
    result = ""
    last_end = 0

    for pos in positions:
        # Add text before match
        result += text[last_end:pos]
        # Add highlighted match
        match = text[pos:pos + len(pattern)]
        result += f"{highlight_char}{match}{highlight_char}"
        last_end = pos + len(pattern)

    # Add remaining text
    result += text[last_end:]
    return result

# Example usage
document = "Python is great. I love Python programming. Python makes coding fun!"
search_term = "Python"

positions = find_patterns(document, search_term)
highlighted = highlight_pattern(document, search_term)

print(f"Original: {document}")
print(f"Pattern '{search_term}' found at positions: {positions}")
print(f"Highlighted: {highlighted}")

Example 3: Password Strength Checker

def check_password_strength(password):
    """Check password strength and provide feedback."""
    score = 0
    feedback = []

    # Length check
    if len(password) >= 12:
        score += 2
    elif len(password) >= 8:
        score += 1
    else:
        feedback.append("Password should be at least 8 characters long")

    # Character type checks
    has_lower = any(c.islower() for c in password)
    has_upper = any(c.isupper() for c in password)
    has_digit = any(c.isdigit() for c in password)
    has_special = any(c in "!@#$%^&*()_+-=[]{}|;:,.<>?" for c in password)

    if has_lower:
        score += 1
    else:
        feedback.append("Add lowercase letters")

    if has_upper:
        score += 1
    else:
        feedback.append("Add uppercase letters")

    if has_digit:
        score += 1
    else:
        feedback.append("Add numbers")

    if has_special:
        score += 1
    else:
        feedback.append("Add special characters (!@#$%^&*)")

    # Common pattern checks
    if password.lower() in ["password", "123456", "qwerty", "admin"]:
        score -= 2
        feedback.append("Avoid common passwords")

    # Repetitive patterns
    if len(set(password)) < len(password) * 0.6:
        score -= 1
        feedback.append("Avoid repetitive characters")

    # Determine strength
    if score >= 6:
        strength = "Very Strong"
    elif score >= 4:
        strength = "Strong"
    elif score >= 2:
        strength = "Medium"
    else:
        strength = "Weak"

    return {
        'strength': strength,
        'score': max(0, score),
        'feedback': feedback
    }

# Example usage
test_passwords = [
    "password",
    "Password123",
    "MyStr0ng!Pass",
    "SuperSecure@2024!",
    "123456"
]

for pwd in test_passwords:
    result = check_password_strength(pwd)
    print(f"Password: '{pwd}'")
    print(f"Strength: {result['strength']} (Score: {result['score']})")
    if result['feedback']:
        print(f"Suggestions: {', '.join(result['feedback'])}")
    print("-" * 40)

Common String Mistakes and How to Avoid Them

Mistake 1: Modifying Strings In-Place

# Wrong - strings are immutable
text = "hello"
# text[0] = "H"  # This will cause an error!

# Correct - create a new string
text = "hello"
text = "H" + text[1:]  # "Hello"
# Or use replace
text = text.replace("h", "H", 1)  # Replace first occurrence only
print(text)  # Hello

Mistake 2: Inefficient String Concatenation

# Inefficient for many concatenations
words = ["Python", "is", "really", "awesome", "for", "programming"]
result = ""
for word in words:
    result += word + " "  # Creates new string each time

# Efficient approach
result = " ".join(words)
print(result)  # Python is really awesome for programming

Mistake 3: Not Handling Case Sensitivity

# Wrong - case sensitive comparison
user_input = "YES"
if user_input == "yes":  # This will be False
    print("User said yes")

# Correct - handle different cases
user_input = "YES"
if user_input.lower() in ["yes", "y", "true", "1"]:
    print("User said yes")

Mistake 4: Forgetting to Strip Input

# Wrong - not handling whitespace
name = input("Enter your name: ")  # User might enter "  John  "
if name == "John":  # This will be False due to extra spaces
    print("Hello John!")

# Correct - always strip user input
name = input("Enter your name: ").strip()
if name == "John":
    print("Hello John!")

# Even better - handle case and whitespace
name = input("Enter your name: ").strip().title()
print(f"Hello {name}!")

Performance Tips for String Operations

Use Join Instead of Concatenation for Multiple Strings

import time

# Inefficient way
def slow_concatenation(words):
    result = ""
    for word in words:
        result += word + " "
    return result.strip()

# Efficient way
def fast_concatenation(words):
    return " ".join(words)

# Test with large list
large_word_list = ["word"] * 10000

# Time the slow method
start = time.time()
slow_result = slow_concatenation(large_word_list)
slow_time = time.time() - start

# Time the fast method
start = time.time()
fast_result = fast_concatenation(large_word_list)
fast_time = time.time() - start

print(f"Slow method: {slow_time:.4f} seconds")
print(f"Fast method: {fast_time:.4f} seconds")
print(f"Speed improvement: {slow_time/fast_time:.1f}x faster")

Use String Methods Instead of Loops When Possible

text = "The quick brown fox jumps over the lazy dog"

# Less efficient - manual loop
def count_vowels_slow(text):
    count = 0
    vowels = "aeiouAEIOU"
    for char in text:
        if char in vowels:
            count += 1
    return count

# More efficient - using sum with generator
def count_vowels_fast(text):
    vowels = "aeiouAEIOU"
    return sum(1 for char in text if char in vowels)

# Even more efficient for specific tasks - use built-in methods
def count_specific_char(text, target):
    return text.lower().count(target.lower())

print(f"Vowels (slow): {count_vowels_slow(text)}")
print(f"Vowels (fast): {count_vowels_fast(text)}")
print(f"Letter 'o': {count_specific_char(text, 'o')}")

Advanced String Techniques

String Templates for Safe Formatting

from string import Template

# Using Template for safe string substitution
template = Template("Hello $name, your balance is $amount")

# Safe substitution - won't raise error if key missing
try:
    message1 = template.substitute(name="Alice", amount="$100")
    print(message1)  # Hello Alice, your balance is $100

    # Missing key raises KeyError
    # message2 = template.substitute(name="Bob")  # KeyError!

    # Safe substitute with default
    message2 = template.safe_substitute(name="Bob", amount="Unknown")
    print(message2)  # Hello Bob, your balance is Unknown

    # Even safer - provide defaults
    message3 = template.safe_substitute(name="Charlie")
    print(message3)  # Hello Charlie, your balance is $amount

except KeyError as e:
    print(f"Missing template variable: {e}")

Regular Expressions with Strings

import re

def extract_information(text):
    """Extract various types of information from text using regex."""

    # Extract email addresses
    email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
    emails = re.findall(email_pattern, text)

    # Extract phone numbers (US format)
    phone_pattern = r'\b\d{3}[-.]?\d{3}[-.]?\d{4}\b'
    phones = re.findall(phone_pattern, text)

    # Extract URLs
    url_pattern = r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
    urls = re.findall(url_pattern, text)

    # Extract dates (MM/DD/YYYY or MM-DD-YYYY)
    date_pattern = r'\b\d{1,2}[/-]\d{1,2}[/-]\d{4}\b'
    dates = re.findall(date_pattern, text)

    return {
        'emails': emails,
        'phones': phones,
        'urls': urls,
        'dates': dates
    }

# Example text
sample_text = """
Contact John at john.doe@email.com or call 555-123-4567.
You can also reach Mary at mary.smith@company.org or 555.987.6543.
Visit our website at https://www.example.com for more information.
Important dates: 12/25/2024 and 01-01-2025.
"""

extracted = extract_information(sample_text)
for category, items in extracted.items():
    print(f"{category.capitalize()}: {items}")

Custom String Formatter Class

class SmartFormatter:
    """A custom string formatter with additional features."""

    def __init__(self, template):
        self.template = template

    def format(self, **kwargs):
        """Format string with automatic type conversion and defaults."""
        result = self.template

        for key, value in kwargs.items():
            placeholder = f"{{{key}}}"

            if placeholder in result:
                # Convert value to string intelligently
                if isinstance(value, float):
                    formatted_value = f"{value:.2f}"
                elif isinstance(value, int) and value > 1000:
                    formatted_value = f"{value:,}"
                else:
                    formatted_value = str(value)

                result = result.replace(placeholder, formatted_value)

        return result

    def format_with_defaults(self, defaults=None, **kwargs):
        """Format with default values for missing keys."""
        if defaults is None:
            defaults = {}

        # Combine defaults with provided kwargs
        all_values = {**defaults, **kwargs}
        return self.format(**all_values)

# Example usage
formatter = SmartFormatter("Hello {name}, your score is {score} and balance is ${balance}")

# Basic formatting
print(formatter.format(name="Alice", score=95, balance=1234.56))
# Output: Hello Alice, your score is 95 and balance is $1,234.56

# With defaults
defaults = {"name": "Guest", "score": 0, "balance": 0.0}
print(formatter.format_with_defaults(defaults, name="Bob", score=87))
# Output: Hello Bob, your score is 87 and balance is $0.00

Real-World String Applications

Log File Parser

import re
from datetime import datetime

class LogParser:
    """Parse and analyze log files."""

    def __init__(self, log_format="apache"):
        if log_format == "apache":
            # Apache Common Log Format
            self.pattern = r'(\S+) \S+ \S+ \[([\w:/]+\s[+\-]\d{4})\] "(\S+) (\S+) (\S+)" (\d{3}) (\d+|-)'
        else:
            # Simple custom format: [TIMESTAMP] LEVEL: MESSAGE
            self.pattern = r'\[([^\]]+)\] (\w+): (.+)'

    def parse_line(self, line):
        """Parse a single log line."""
        match = re.match(self.pattern, line.strip())
        if match:
            return match.groups()
        return None

    def analyze_file(self, filename):
        """Analyze an entire log file."""
        stats = {
            'total_lines': 0,
            'parsed_lines': 0,
            'error_lines': 0,
            'status_codes': {},
            'methods': {},
            'ips': {}
        }

        try:
            with open(filename, 'r') as file:
                for line in file:
                    stats['total_lines'] += 1
                    parsed = self.parse_line(line)

                    if parsed:
                        stats['parsed_lines'] += 1
                        # Extract information based on log format
                        if len(parsed) == 7:  # Apache format
                            ip, timestamp, method, url, protocol, status, size = parsed

                            # Count status codes
                            stats['status_codes'][status] = stats['status_codes'].get(status, 0) + 1

                            # Count methods
                            stats['methods'][method] = stats['methods'].get(method, 0) + 1

                            # Count IPs
                            stats['ips'][ip] = stats['ips'].get(ip, 0) + 1
                    else:
                        stats['error_lines'] += 1

        except FileNotFoundError:
            return "Log file not found"

        return stats

# Example usage (would need actual log file)
# parser = LogParser()
# analysis = parser.analyze_file("access.log")
# print(f"Log analysis: {analysis}")

Configuration File Manager

class ConfigManager:
    """Manage configuration files with string parsing."""

    def __init__(self):
        self.config = {}

    def load_from_string(self, config_string):
        """Load configuration from a string."""
        self.config = {}

        for line in config_string.strip().split('\n'):
            line = line.strip()

            # Skip empty lines and comments
            if not line or line.startswith('#'):
                continue

            # Parse key=value pairs
            if '=' in line:
                key, value = line.split('=', 1)
                key = key.strip()
                value = value.strip()

                # Remove quotes if present
                if value.startswith('"') and value.endswith('"'):
                    value = value[1:-1]
                elif value.startswith("'") and value.endswith("'"):
                    value = value[1:-1]

                # Try to convert to appropriate type
                value = self._convert_value(value)
                self.config[key] = value

    def _convert_value(self, value):
        """Convert string value to appropriate Python type."""
        # Try boolean
        if value.lower() in ('true', 'yes', 'on', '1'):
            return True
        elif value.lower() in ('false', 'no', 'off', '0'):
            return False

        # Try integer
        try:
            return int(value)
        except ValueError:
            pass

        # Try float
        try:
            return float(value)
        except ValueError:
            pass

        # Return as string
        return value

    def get(self, key, default=None):
        """Get configuration value with optional default."""
        return self.config.get(key, default)

    def set(self, key, value):
        """Set configuration value."""
        self.config[key] = value

    def to_string(self):
        """Convert configuration back to string format."""
        lines = []
        for key, value in sorted(self.config.items()):
            if isinstance(value, str) and (' ' in value or '=' in value):
                lines.append(f'{key}="{value}"')
            else:
                lines.append(f'{key}={value}')
        return '\n'.join(lines)

# Example usage
config_text = """
# Database configuration
db_host=localhost
db_port=5432
db_name="myapp_db"
db_ssl=true

# Application settings
debug=false
max_connections=100
timeout=30.5
app_name="My Awesome App"
"""

config = ConfigManager()
config.load_from_string(config_text)

print(f"Database host: {config.get('db_host')}")
print(f"Debug mode: {config.get('debug')}")
print(f"Max connections: {config.get('max_connections')}")
print(f"Timeout: {config.get('timeout')}")

# Modify and save
config.set('debug', True)
config.set('new_feature', 'enabled')

print("\nUpdated configuration:")
print(config.to_string())

Summary

Working with strings is a fundamental skill in Python programming. Here are the key takeaways:

Core Concepts:
- Strings are immutable - you create new strings rather than modifying existing ones
- Multiple creation methods - single quotes, double quotes, triple quotes, raw strings
- Rich method library - Python provides extensive built-in string methods
- Flexible formatting - f-strings, .format(), and % formatting for different needs

Essential Operations:
- Indexing and slicing for extracting parts of strings
- Concatenation and repetition for building strings
- Case conversion and whitespace handling for text processing
- Search and replace methods for text manipulation
- Split and join for working with string collections

Best Practices:
- Use f-strings for modern, readable string formatting
- Always strip user input to handle whitespace
- Use appropriate string methods instead of manual loops
- Handle case sensitivity explicitly in comparisons
- Use join() for efficient concatenation of multiple strings
- Validate and sanitize string input appropriately

Advanced Techniques:
- Regular expressions for complex pattern matching
- String templates for safe substitution
- Custom formatters for specialized needs
- Efficient algorithms for string processing

Common Applications:
- User input validation and processing
- File parsing and text analysis
- Configuration management
- Data cleaning and transformation
- Pattern matching and text search

Mastering string operations will make you much more effective at text processing, user interface development, data manipulation, and many other programming tasks. The key is to familiarize yourself with Python's extensive string methods and choose the right tool for each specific task!