Python Functions and Argument Passing
Introduction to Functions and Code Reuse
Concept of Functions:
A function is a named block of code designed to perform a specific task when called (invoked).
Functions enable code reusability, improve organizational structure, and enhance code readability.
Functions can take zero or more parameters as input and optionally produce return values.
Primary Motivations for Writing Functions:
Code Duplication Isolation (Condition 1): When a specific piece of code is repeated multiple times throughout a program, it should be isolated into a standalone function.
Single-Point Maintenance: By encapsulating repeated code into a function, any future modifications or bug fixes need to be applied only once within the function body, automatically propagating the change across all invocation points.
Software Decomposition and Design Principles
Code Decomposition (Condition 2):
When a codebase or problem becomes too large and complex to comprehend easily, it must be decomposed into smaller, isolated, and self-contained sub-problems.
Each sub-problem is implemented as a separate function.
This iterative process continues until the codebase consists of short, single-purpose functions that are easy to understand, debug, and unit-test independently.

Teamwork and Responsibility Division (Condition 3):
Large software engineering projects cannot be executed efficiently by a single developer; work must be distributed across a team.
Decomposition allows different programmers to simultaneously write separate, well-defined functions without interfering with each other's code.
Individual functions are packaged together into modules to construct the final cohesive product.

Origins and Categories of Functions in Python
Four Basic Categories of Functions in Python:
Built-in Functions:
Functions integrated directly into the Python core language.
Always available for execution without requiring import statements or extra setup (e.g.,
print(),input()).The full official directory is available at
https://docs.python.org/3/library/functions.html.Pre-installed Module Functions:
Functions packaged within modules that ship standard with Python installations.
Used less frequently than built-in functions; require explicit import statements (e.g.,
import math) to make them accessible within code.User-Defined Functions:
Custom functions written directly by programmers to satisfy specific application requirements.
Lambda Functions:
Small, anonymous, single-expression functions defined inline.

Defining User-Defined Functions and Execution Mechanics
Syntax Rules for Function Definition:
A function definition must begin with the keyword
def(short for define).The keyword is followed by the
functionName, which must comply with standard Python variable naming conventions.The name is immediately followed by a pair of parentheses
(), which enclose any optional parameters.The header line must conclude with a colon
:The function body begins on the subsequent line and must be indented (nested) by at least one level. It contains one or more instructions executed whenever the function is invoked.
The boundaries of a function body are defined strictly by indentation; the function ends where nesting/indentation returns to the previous outer level.
def functionName():
functionBody
Control Flow During Function Invocation:
Invocation (Call): When Python encounters a function call, execution of the main program halts temporarily, saving the current position.
Jump & Execution: Program control jumps to the function body, executing its instructions sequentially from top to bottom.
Return: Reaching the end of the function body (or encountering an explicit return) forces control to jump back to the exact invocation site, resuming execution at the statement directly following the function call.

Essential Operational Constraints (Catches):
Definition Order Requirement: A function cannot be invoked before it has been defined. Because Python interprets script files top-to-bottom line-by-line, it will not look ahead to discover functions declared further down the file.
Name Collision / Overwriting: A function and a variable cannot share the same identifier in the same scope. Defining a variable with the same name as an existing function (or vice versa) overwrites the original definition.
Function Parameters, Arguments, and Variable Shadowing
Parameters vs. Arguments:
Parameter: A specialized variable defined within the parentheses of a function header statement (
def). Parameters live exclusively inside their defining function.Argument: A value or expression passed into a function during invocation to initialize a parameter. Arguments exist outside the function body.

Single-Parameter Functions:
Modifies function behavior based on input values passed by the caller.
def message(number):
print("Enter a number:", number)
message(1)
# Output: Enter a number: 1
Variable Shadowing:
Occurs when a parameter shares the exact same name as a global or outer-scope variable.
Within the function, the local parameter shadows (hides) the outer variable.
The parameter and the outer variable remain two completely distinct memory entities; modifying or accessing the parameter inside the function has no effect on the outer variable.
def message(number):
print("Enter a number:", number)
number = 1234
message(1)
# Output: Enter a number: 1
print(number)
# Output: 1234
Multi-Parameter Functions:
Functions can declare multiple parameters separated by commas.
Callers must provide an equal number of arguments matching the parameter signature.
def message(what, number):
print("Enter", what, "number", number)
message("telephone", 11)
# Output: Enter telephone number 11
message("price", 5)
# Output: Enter price number 5
message("number", "number")
# Output: Enter number number number
Parameter Passing Techniques: Positional, Keyword, and Mixed
Positional Argument Passing:
Maps the argument supplied during invocation directly to the parameter declared in the function header based on sequential position.
def myFunction(a, b, c):
print(a, b, c)
myFunction(1, 2, 3)
# Output: 1 2 3
Keyword Argument Passing (Named Arguments):
Passes values explicitly assigned to parameter names using the syntax
param_name=value.The order of arguments during invocation does not matter, as target destinations are determined strictly by name matching.
def introduction(firstName, lastName):
print("Hello, my name is", firstName, lastName)
introduction(firstName="James", lastName="Bond")
# Output: Hello, my name is James Bond
introduction(lastName="Skywalker", firstName="Luke")
# Output: Hello, my name is Luke Skywalker
Mixing Positional and Keyword Arguments:
Positional and keyword argument styles can be combined within a single function invocation.
Unbreakable Rule: All positional arguments MUST appear before any keyword arguments in a function call.
def adding(a, b, c):
print(a, "+", b, "+", c, "=", a + b + c)
# Purely positional
adding(1, 2, 3)
# Output: 1 + 2 + 3 = 6
# Purely keyword
adding(c=1, a=2, b=3)
# Output: 2 + 3 + 1 = 6
# Mixed (valid: positional first, then keyword)
adding(3, c=1, b=2)
# Output: 3 + 2 + 1 = 6
Syntax and Type Errors in Argument Passing:
SyntaxError (Positional After Keyword): Placing a positional argument after a keyword argument causes a parser failure before execution.
def subtra(a, b):
print(a - b)
subtra(a=5, 2)
# Output: SyntaxError: positional argument follows keyword argument
TypeError (Multiple Value Assignment): Supplying a positional argument that maps to a parameter already assigned via a keyword argument causes a runtime error.
def adding(a, b, c):
print(a + b + c)
adding(3, a=1, b=2)
# Output: TypeError: adding() got multiple values for argument 'a'
Default Parameter Values and Fallback Handling
Defining Default Parameter Values:
Functions can specify pre-defined fallback values for parameters in the
defheader line using the syntaxparameter=default_value.If the invoker omits a parameter during execution, Python automatically uses its pre-defined default value.
def introduction(firstName, lastName="Smith"):
print("Hello, my name is", firstName, lastName)
introduction("James", "Doe")
# Output: Hello, my name is James Doe
introduction("Henry")
# Output: Hello, my name is Henry Smith
introduction(firstName="William")
# Output: Hello, my name is William Smith
Multiple Default Parameters:
Every parameter in a function header can be assigned a default value.
Omitted arguments fall back to defaults, while provided arguments override default assignments.
def introduction(firstName="John", lastName="Smith"):
print("Hello, my name is", firstName, lastName)
introduction()
# Output: Hello, my name is John Smith
introduction(lastName="Hopkins")
# Output: Hello, my name is John Hopkins
Summary of Function Parameter Examples:
# 1-parameter function
def hi(name):
print("Hi,", name)
hi("Greg")
# 2-parameter function
def hiAll(name1, name2):
print("Hi,", name1)
print("Hi,", name2)
hiAll("Sebastian", "Konrad")
# 3-parameter function
def address(street, city, postalCode):
print("Your address is:", street, "St.,", city, postalCode)
s = input("Street: ")
pC = input("Postal Code: ")
c = input("City: ")
address(s, c, pC)