Chapter 8: Functions
Functions are named blocks of code that do one specific job. Write once, call anywhere.
Defining a Function
def greet():
"""Display a simple greeting.""" (1)
print("Hello!")
greet() # Call the function
| 1 | Docstring - describes what the function does |
Passing Arguments
Parameters vs Arguments
-
Parameter: Variable in function definition
-
Argument: Value passed when calling
def ping(host): # host is parameter
print(f"Pinging {host}...")
ping('web-01') # 'web-01' is argument
Positional Arguments
Order matters:
def connect(host, port):
print(f"Connecting to {host}:{port}")
connect('10.0.1.10', 443) # host='10.0.1.10', port=443
connect(443, '10.0.1.10') # Wrong order - host=443
Keyword Arguments
Explicit naming, order doesn’t matter:
connect(host='10.0.1.10', port=443)
connect(port=443, host='10.0.1.10') # Same result
Default Values
def connect(host, port=22): (1)
print(f"Connecting to {host}:{port}")
connect('web-01') # Uses default port 22
connect('web-01', 443) # Overrides to 443
| 1 | Parameters with defaults must come after those without |
Argument Errors
def connect(host, port):
print(f"{host}:{port}")
connect('web-01') # TypeError: missing required argument 'port'
Python’s error tells you exactly what’s missing.
Return Values
Functions can process data and return results:
def get_fqdn(hostname, domain='example.com'):
"""Return fully qualified domain name."""
return f"{hostname}.{domain}"
fqdn = get_fqdn('web-01')
print(fqdn) # web-01.example.com
Optional Arguments
Use empty string or None as default for optional args:
def format_address(host, port, protocol=''):
"""Format connection string."""
if protocol:
return f"{protocol}://{host}:{port}"
return f"{host}:{port}"
format_address('web-01', 443) # 'web-01:443'
format_address('web-01', 443, 'https') # 'https://web-01:443'
Returning Dictionaries
def build_server(hostname, ip, role='web'):
"""Build a server info dictionary."""
return {
'hostname': hostname,
'ip': ip,
'role': role
}
server = build_server('web-01', '10.0.1.10')
Returning None
Functions without explicit return (or bare return) return None:
def log_message(msg):
print(f"[LOG] {msg}")
# no return statement
result = log_message("test")
print(result) # None
Passing Lists
Lists passed to functions can be modified:
def deploy_servers(pending, completed):
"""Deploy servers from pending to completed list."""
while pending:
server = pending.pop()
print(f"Deploying {server}...")
completed.append(server)
to_deploy = ['web-01', 'web-02', 'db-01']
deployed = []
deploy_servers(to_deploy, completed)
print(to_deploy) # [] - empty, was modified
print(deployed) # ['db-01', 'web-02', 'web-01']
Preventing Modification
Pass a copy using slice:
deploy_servers(to_deploy[:], deployed) # [:] creates copy
# to_deploy unchanged
Only do this when you need the original preserved.
Arbitrary Arguments
*args - Variable Positional
*args collects extra positional arguments into a tuple:
def add_ports(*ports):
"""Add ports to firewall."""
for port in ports:
print(f"Opening port {port}")
add_ports(22)
add_ports(80, 443, 8080)
Mix with regular parameters (regular first):
def configure_service(name, *ports):
print(f"Service: {name}")
for port in ports:
print(f" Port: {port}")
configure_service('nginx', 80, 443)
**kwargs - Variable Keyword
**kwargs collects extra keyword arguments into a dictionary:
def build_config(hostname, **settings):
"""Build configuration with arbitrary settings."""
config = {'hostname': hostname}
config.update(settings)
return config
config = build_config('web-01',
ip='10.0.1.10',
port=443,
ssl=True)
# {'hostname': 'web-01', 'ip': '10.0.1.10', 'port': 443, 'ssl': True}
Modules
Store functions in separate files for reuse.
Importing a Module
# network.py
def ping(host):
print(f"Pinging {host}...")
def scan(host, port):
print(f"Scanning {host}:{port}")
# main.py
import network
network.ping('web-01')
network.scan('web-01', 443)
Import Specific Functions
from network import ping, scan
ping('web-01') # No module prefix needed
Aliases
# Module alias
import network as net
net.ping('web-01')
# Function alias
from network import scan as port_scan
port_scan('web-01', 443)
Import All (Avoid)
from network import * # Imports everything - can cause name conflicts
Use explicit imports or module prefix instead.
Styling Functions
def function_name(param1, param2='default'): (1)
"""Docstring describing function.""" (2)
# function body
return result
| 1 | No spaces around = in default values |
| 2 | Docstring immediately after definition |
Long parameter lists:
def configure_server(
hostname, ip, port,
ssl=True, timeout=30):
"""Configure server with given parameters."""
# ...
Two blank lines between functions in a module.
Quick Reference
| Pattern | Code |
|---|---|
Define |
|
Docstring |
|
Default value |
|
Keyword arg |
|
Return value |
|
Variable positional |
|
Variable keyword |
|
Import module |
|
Import function |
|
Alias |
|
Exercises
8-1. Message
Write display_message() that prints what you’re learning. Call it.
8-2. Servers
Write describe_server(hostname, ip). Call with positional and keyword args.
8-3. Service Config
Write configure_service(name, port=80) with default port. Call both ways.
8-4. Connection String
Write function returning "protocol://host:port". Make protocol optional.
8-5. Server Builder
Write function accepting hostname plus arbitrary **kwargs, returning dict.
8-6. Network Module
Put 3 network-related functions in network.py. Import and use each way.
Summary
-
Functions:
def name(params):with docstring -
Positional arguments must match order
-
Keyword arguments are explicit:
name=value -
Default values make parameters optional
-
argscollects variable positional,*kwargscollects keyword -
returnsends data back to caller -
Modules organize functions in separate files
Next: Classes for object-oriented programming.