Module 3 - String and function
PREQUIZ
Question 1: What is the primary difference between break and continue statements
in loop control?
a) Break pauses the loop, continue stops it permanently
b) Break exits the loop entirely, continue skips the current iteration and moves to the
next
c) Both statements do the same thing
d) Continue exits the loop, break skips to the next iteration
Question 2: What will be the output of this code?
for i in range(5):
if i == 3:
break
print(i)
a) 0 1 2 3 4
b) 0 1 2
c) 0 1 2 3
d) 1 2 3
Question 3: Which statement would you use to skip printing the number 5 in a loop
from 1 to 10?
a) if i == 5: break
b) if i == 5: pass
c) if i == 5: continue
d) if i == 5: return
Question 4: Which of the following is the correct way to define a function in Python?
a) function myFunc():
b) def myFunc():
c) define myFunc():
d) func myFunc():
Question 5: Which method converts "python" to "PYTHON"?
a) [Link]()
b) [Link]()
c) [Link]()
d) [Link]()
Answer: b) [Link]()
Question 6: What happens if a recursive function lacks a base case?
a) It returns None
b) It causes infinite recursion/stack overflow
c) It won't compile
d) It returns 0
LEARN
Module 2.1: Introduction to Functions - Complete Understanding
What Are Functions?
Functions are self-contained blocks of code that accomplish specific tasks. Think of
them as mini-programs within your program. Just as a recipe combines ingredients
and steps to create a dish, a function combines parameters and statements to
produce a result. Functions are the building blocks of structured programming and
provide numerous benefits.
Why Use Functions?
Code Reusability: Instead of writing the same code multiple times, you write it once
in a function and call it whenever needed. This follows the DRY principle - Don't
Repeat Yourself.
Modularity: Functions break complex problems into smaller, manageable pieces.
Each function handles one specific task, making the overall program easier to
understand and maintain.
Abstraction: Functions hide implementation details. Users of a function don't need
to know how it works internally, only what inputs it needs and what output it provides.
Testing and Debugging: Isolated functions are easier to test and debug. You can
verify each function works correctly independently before integrating them.
Collaboration: Different team members can work on different functions
simultaneously without interfering with each other's code.
Function Terminology
Function Definition: The actual code that describes what the function does
Function Call/Invocation: Using the function in your program
Function Name: The identifier used to call the function
Function Body: The indented block of code that executes when the function
is called
Function Signature: The combination of function name and parameters
Module 2.2: Defining Functions - In-Depth Guide
Basic Function Syntax
Every function definition in Python follows a specific structure. The def keyword
signals the start of a function definition. This is followed by the function name,
parentheses (which may contain parameters), and a colon. The function body must
be indented, typically by four spaces.
Naming Conventions
Function names should be:
Descriptive: The name should clearly indicate what the function does
Lowercase: Python convention uses lowercase letters
Snake_case: Multiple words separated by underscores
Verb-based: Functions perform actions, so use verbs like calculate, process,
validate
Avoiding reserved words: Don't use Python keywords as function names
Good examples: calculate_average, validate_email, process_payment,
get_user_input
Poor examples: func1, x, data, function
Documentation Strings (Docstrings)
A docstring is a string literal that appears as the first statement in a function. It
describes what the function does, its parameters, return value, and any exceptions it
might raise. Docstrings are accessible through the function's doc attribute and are
used by documentation generation tools.
Docstring conventions:
Use triple quotes for multi-line docstrings
First line should be a brief summary
More detailed explanation follows after a blank line
Document parameters, return values, and exceptions
Include usage examples for complex functions
Function Body Structure
The function body contains:
1. Input validation: Check if parameters meet requirements
2. Main logic: The core functionality
3. Error handling: Deal with potential problems
4. Return statement: Send results back to caller
Module 2.3: Parameters and Arguments - Comprehensive Guide
Understanding Parameters vs Arguments
Parameters are the variables listed in the function definition. They are placeholders
that will receive values when the function is called. Think of them as empty boxes
waiting to be filled.
Arguments are the actual values passed to the function when calling it. They are the
contents that fill those parameter boxes.
Types of Parameters
1. Positional Parameters
These are the most common type. Arguments must be provided in the same order as
parameters are defined. The position matters, hence the name. Each positional
parameter must receive exactly one argument unless it has a default value.
2. Keyword Parameters
When calling a function, you can specify arguments by parameter name. This allows
arguments to be provided in any order and makes function calls more readable.
Keyword arguments must come after positional arguments in the function call.
3. Default Parameters
Parameters can have default values specified in the function definition. If no
argument is provided for that parameter, the default value is used. Default
parameters must come after non-default parameters in the definition.
4. Variable-Length Parameters
*args: Accepts any number of positional arguments as a tuple
**kwargs: Accepts any number of keyword arguments as a dictionary
Parameter Passing Mechanism
Python uses "pass-by-object-reference" mechanism:
Immutable objects (strings, numbers, tuples): Changes inside the function
don't affect the original
Mutable objects (lists, dictionaries): Changes inside the function affect the
original
Best Practices for Parameters
1. Limit the number of parameters (ideally 3 or fewer)
2. Use descriptive parameter names
3. Group related parameters into objects if there are many
4. Document parameter types and expectations
5. Validate parameter values at the start of the function
6. Use default values for optional parameters
7. Consider using keyword-only parameters for clarity
Module 2.4: The Return Statement - Complete Analysis
Purpose of Return
The return statement serves multiple purposes:
1. Exits the function: Immediately terminates function execution
2. Sends value back: Provides a result to the calling code
3. Indicates success/failure: Can return status codes or boolean values
4. Enables function chaining: Returned values can be used as inputs to other
functions
Return Statement Behavior
No Return Statement:
If a function doesn't have a return statement, it implicitly returns None. This is
common for functions that perform actions rather than calculations.
Empty Return:
A return statement without a value also returns None but explicitly exits the function
early. Useful for conditional exits.
Multiple Return Statements:
A function can have multiple return statements, but only one executes per call. Often
used with conditional logic to return different values based on conditions.
Returning Multiple Values:
Python can return multiple values as a tuple. This is actually returning a single tuple
object that can be unpacked by the caller.
Return Value Types
Functions can return:
Primitive types: integers, floats, strings, booleans
Collections: lists, tuples, dictionaries, sets
Objects: custom class instances
Functions: functions can return other functions (higher-order functions)
None: indicates no meaningful value to return
Best Practices for Return Statements
1. Be consistent with return types
2. Document what the function returns
3. Return early for error conditions
4. Avoid complex expressions in return statements
5. Consider returning named tuples for multiple values
6. Use None explicitly for no value
7. Don't mix return with and without values
Module 2.5: Default Argument Values - Advanced Concepts
How Default Arguments Work
Default arguments are evaluated once when the function is defined, not each time
it's called. This has important implications, especially with mutable default values.
Default arguments allow functions to be called with fewer arguments than
parameters, providing flexibility and backward compatibility.
Rules for Default Arguments
1. Order: Default parameters must come after non-default parameters
2. Evaluation: Default values are evaluated at definition time
3. Scope: Default values are evaluated in the scope where the function is
defined
4. Overriding: Providing an argument overrides the default value
The Mutable Default Argument Trap
Using mutable objects (lists, dictionaries) as default values can lead to unexpected
behavior because the same object is reused across function calls. Each call that
uses the default will share the same mutable object, leading to unintended data
sharing.
Solution: Use None as default and create new mutable objects inside the function.
Use Cases for Default Arguments
1. Configuration options: Provide sensible defaults for configuration
parameters
2. Optional features: Enable optional functionality without requiring all
arguments
3. Backward compatibility: Add new parameters without breaking existing
code
4. Convenience methods: Simplify common use cases
Advanced Default Argument Patterns
Sentinel Values:
Using None or custom sentinel objects to distinguish between "no argument
provided" and "None was explicitly passed"
Factory Functions:
Using callables as defaults that generate fresh values
Conditional Defaults:
Computing default values based on other parameters or system state
LEARN2
What is Scope?
Scope is the region of a program where a variable is accessible. It determines the
visibility and lifetime of variables. Understanding scope is crucial for writing bug-free
code and managing program state effectively. Python uses lexical (static) scoping,
meaning scope is determined by where variables are defined in the code, not where
they're called.
The LEGB Rule
Python resolves names using the LEGB rule, checking each scope in order:
L - Local Scope:
Inside the current function
Created when function is called
Destroyed when function returns
Each function call creates new local scope
E - Enclosing Scope:
In the enclosing function (for nested functions)
Accessible to inner functions
Allows closures and function factories
Created by nested function definitions
G - Global Scope:
At the top level of the module
Available throughout the module
Persists for program duration
Shared across all functions in the module
B - Built-in Scope:
Pre-defined names in Python
Always available
Includes built-in functions and exceptions
Lowest priority in name resolution
Namespace and Scope Relationship
A namespace is a container that maps names to objects. Each scope has its own
namespace. When you use a name, Python searches through namespaces in LEGB
order until it finds the name or raises a NameError.
Variable Lifetime
Local variables: Live from creation until function returns
Global variables: Live for entire program execution
Enclosed variables: Live as long as the enclosing function exists
Module 3.2: Local Scope - Deep Dive
Creating Local Scope
Every function call creates a new local scope. Variables assigned within a function
are local by default. Parameters are also local variables. Local scope is isolated from
other scopes, providing encapsulation.
Local Variable Characteristics
Isolation: Local variables can't be accessed from outside the function
Shadowing: Local variables can have the same name as global variables, hiding
them
Temporary: Local variables are destroyed when function exits
Stack-based: Stored on the call stack, automatically managed
Local Scope Best Practices
1. Prefer local variables: They're safer and more predictable
2. Initialize variables: Always initialize before use
3. Minimize scope: Declare variables close to where they're used
4. Avoid shadowing: Don't reuse names from outer scopes
5. Document side effects: If function modifies external state
Common Local Scope Patterns
Accumulator Pattern:
Using local variables to build up results iteratively
Temporary Storage:
Local variables for intermediate calculations
Loop Variables:
Loop control variables are local to the function
Guard Variables:
Boolean flags for controlling flow
3.2 Working with Global Variables
Global variables are defined at module level, outside all functions. They're accessible
from any function in the module but require special handling to modify.
The global Keyword
To modify a global variable inside a function, you must declare it with the global
keyword. This tells Python to use the global namespace for that variable. Without
global, assignment creates a new local variable.
When to Use Global Variables
Appropriate uses:
Configuration constants
Shared state between functions
Cache or memoization storage
Module-level settings
Inappropriate uses:
Function communication (use parameters/returns instead)
Temporary storage
Loop counters
Function-specific data
Global Variable Pitfalls
1. Hidden dependencies: Functions depend on external state
2. Testing difficulties: Tests need to manage global state
3. Thread safety: Global variables aren't thread-safe
4. Debugging challenges: Hard to track modifications
5. Namespace pollution: Too many globals clutter namespace
Alternatives to Global Variables
Class attributes: Encapsulate related data in classes
Function parameters: Pass data explicitly
Return values: Return modified data
Closures: Capture state in nested functions
Modules: Use separate modules for configuration
3.4
What is Recursion?
Recursion is a problem-solving technique where a function calls itself to solve
smaller instances of the same problem. It's based on the principle of mathematical
induction and is particularly elegant for problems with recursive structure.
4.1
What is Immutability?
Immutability means an object's state cannot be modified after creation. In Python,
strings are immutable - once created, their characters cannot be changed. Any
operation that appears to modify a string actually creates a new string object.
Why Are Strings Immutable?
Security: Strings can't be accidentally or maliciously modified
Hashability: Immutable objects can be used as dictionary keys
Thread Safety: Multiple threads can safely access the same string
Memory Optimization: Python can intern and reuse string objects
Predictability: String values remain constant throughout program execution
Implications of Immutability
Memory Usage:
String operations create new objects, potentially using more memory. Concatenating
strings in loops can be inefficient.
Performance Considerations:
Building strings character by character is inefficient. Use join() method or string
builders for better performance.
Reference Sharing:
Multiple variables can safely reference the same string object. Assignment creates
new references, not copies.
Working with Immutability
String Building Strategies:
1. Use join() for combining many strings
2. Use formatting for complex string construction
3. Use StringIO for file-like string building
4. List comprehensions for string transformation
Common Pitfalls:
Attempting to modify string characters directly
Inefficient string concatenation in loops
Forgetting to assign results of string methods
String Indexing Fundamentals
Positive Indexing:
Starts at 0 for first character
Increments by 1 for each position
Last character at length-1
Out of bounds raises IndexError
Negative Indexing:
Starts at -1 for last character
Decrements moving backward
First character at -length
Provides convenient access from end
Understanding Index Positions
Think of indices as positions between characters:
Index points to the start of a character
Positive indices count from left
Negative indices count from right
Slicing uses these boundary positions
Step Parameter:
Increment between characters
Defaults to 1 if omitted
Negative step reverses direction
Zero step raises ValueError
Advanced Slicing Techniques
Reversing Strings:
Using [::-1] reverses entire string efficiently
Every Nth Character:
Using [::n] selects every nth character
Palindrome Check:
Compare string with its reverse using slicing
Substring Extraction:
Extract portions using calculated indices
String Rotation:
Rotate strings using slicing combinations
Slice Object Creation
Slices can be created as objects and reused:
slice(start, stop, step) creates slice object
Applied using bracket notation
Useful for complex slicing patterns
Can be stored and passed as parameters
Dynamic Slicing
Calculated Indices:
Use variables and expressions for slice boundaries
Conditional Slicing:
Choose slice based on conditions
Parameterized Slicing:
Pass slice parameters to functions
Common Slicing Patterns
Trimming Operations:
Remove first/last n characters
Strip specific prefixes/suffixes
Extract middle portions
Chunking Strings:
Divide into equal parts
Split at specific intervals
Create overlapping windows
Pattern Matching:
Extract patterns using slicing
Find repeating sequences
Validate string structure
Length and Size Methods
len() Function:
Returns character count including spaces
Counts Unicode characters correctly
O(1) time complexity
Fundamental for iteration and validation
Case Conversion Methods
upper() Method:
Converts all characters to uppercase
Returns new string (immutability)
Locale-independent for ASCII
Useful for case-insensitive comparison
lower() Method:
Converts all characters to lowercase
Handles Unicode properly
Common for normalization
Used in search operations
capitalize() Method:
First character uppercase, rest lowercase
Useful for proper formatting
Handles empty strings gracefully
title() Method:
Capitalizes first letter of each word
Uses Unicode definition of words
May not handle contractions correctly
swapcase() Method:
Inverts case of all characters
Limited practical use
Demonstration of string transformation
Search and Replace Methods
find() Method:
Returns lowest index of substring
Returns -1 if not found
Optional start and end parameters
Case-sensitive search
index() Method:
Like find() but raises ValueError if not found
Use when substring must exist
Supports start and end parameters
rfind() and rindex():
Search from right (highest index)
Useful for finding last occurrence
Same parameters as find()/index()
replace() Method:
Replaces all occurrences of substring
Optional count parameter limits replacements
Returns new string
Can replace with empty string to remove
count() Method:
Counts non-overlapping occurrences
Optional start and end parameters
Case-sensitive counting
Returns integer count
Trimming and Padding Methods
strip() Method:
Removes leading and trailing whitespace
Optional chars parameter for custom removal
Doesn't modify internal whitespace
Common for input cleaning
lstrip() and rstrip():
Remove from left or right only
Same parameters as strip()
Useful for specific trimming needs
center() Method:
Centers string in field of given width
Optional fillchar parameter
Returns original if width too small
ljust() and rjust():
Left or right justify in field
Padding with spaces or custom character
Useful for formatting output
zfill() Method:
Pads numeric string with zeros
Handles signs correctly
isdigit() Method:
True if all characters are digits
Includes Unicode digits
False for empty strings
isalnum() Method:
True if all alphanumeric
Combination of isalpha() and isdigit()
No spaces or punctuation
isspace() Method:
True if all characters are whitespace
False for empty strings
Includes various Unicode spaces
islower() and isupper():
Check case of cased characters
Ignore non-cased characters
Require at least one cased character
istitle() Method:
Checks title case formatting
Each word starts with uppercase
Following characters lowercase
Challenge1:
Code Task: Write a recursive function to calculate the $n^{th}$ number in the
Fibonacci sequence.
Challenge2:
Code Task: Write a function that accepts a full name string, uses string slicing to
extract the first name and last name, and returns the name formatted as: "Last
Name, First Name (Initials)".
Apply1:
Project Task: Develop a Palindrome Checker function. The function takes a string,
converts it to lowercase, and returns True if it reads the same forwards and
backwards, using string slicing or reversing.
Apply2:
Project Task: Create a simple Password Generator function that uses default
arguments to allow the user to specify the length and includes at least two required
string methods (e.g., upper(), replace()) for complexity.
POSTQUIZ:
Question 1: What is the primary difference between break and continue statements
in loop control?
a) Break pauses the loop, continue stops it permanently
b) Break exits the loop entirely, continue skips the current iteration and moves to the
next
c) Both statements do the same thing
d) Continue exits the loop, break skips to the next iteration
Question 2: What will be the output of this code?
for i in range(5):
if i == 3:
break
print(i)
a) 0 1 2 3 4
b) 0 1 2
c) 0 1 2 3
d) 1 2 3
Question 3: What is the scope of variable 'x' in this code?
def func():
x = 10
return x
a) Global
b) Local
c) Built-in
d) Universal
Question 4: Which method converts "python" to "PYTHON"?
a) [Link]()
b) [Link]()
c) [Link]()
d) [Link]()
Answer: b) [Link]()
Question 5: What does "Python"[-1] return?
a) "P"
b) "n"
c) Error
d) "Python"
Question 6: How do you reverse a string using slicing?
a) [Link]()
b) string[::-1]
c) string[-1:0]
d) reverse(string)
Question 7: What will len("Hello World") return?
a) 10
b) 11
c) 12
d) 2