Python Functions & Scope
A function is a named, reusable block of code. You define it once and call it as many times as you need. Functions keep programs organized, avoid repetition, and make code easier to test.
At a Glance
Section titled “At a Glance”| Concept | Syntax | Example |
|---|---|---|
| Define | def name(params): | def greet(name): |
| Call | name(args) | greet('Alice') |
| Return a value | return value | return a + b |
| Default value | param=value | def greet(name, greeting='Hello') |
| Keyword argument | name=value at the call | greet('Bob', greeting='Hi') |
| Variable positional args | *args | Collected into a tuple |
| Variable keyword args | **kwargs | Collected into a dict |
| Modify an outer variable | global x, nonlocal x | See Scope |
Built-in Functions Built-In
Section titled “Built-in Functions ”Python ships with many functions that are always available, with no import needed.
| Function | Purpose | Example |
|---|---|---|
print(...) | Display output | print('a', 'b', sep='-') prints a-b |
input(prompt) | Read a line of text from the user | input('Name: ') returns a string |
len(x) | Number of items | len('hello') gives 5 |
type(x) | The type of a value | type(3.5) gives <class 'float'> |
int(x) | Convert to an integer | int(3.14) gives 3, int('42') gives 42 |
float(x) | Convert to a float | float('7.8') gives 7.8 |
str(x) | Convert to a string | str(10) gives '10' |
bool(x) | Convert to a Boolean | bool('') gives False |
round(x, n) | Round a number | round(4.253, 1) gives 4.3 |
abs(x) | Absolute value | abs(-15) gives 15 |
min(), max(), sum() | Smallest, largest, total | max(4, 9, 2) gives 9 |
sorted(x) | A new sorted list | sorted([3, 1, 2]) gives [1, 2, 3] |
range(n) | A sequence of integers | list(range(3)) gives [0, 1, 2] |
int() also accepts Booleans: int(True) gives 1 and int(False) gives 0. Use help(function_name) in a Python shell to read any function’s documentation.
Reading User Input
Section titled “Reading User Input”input() always returns a string, even if the user types digits. Convert it explicitly when you need a number.
name = input('Name: ') # 'Alice'age = int(input('Age: ')) # '30' becomes 30
try: age = int(input('Age: '))except ValueError: print('Please enter a whole number') # Runs if the user types 'abc'Defining Functions
Section titled “Defining Functions”Use the def keyword, a name, parentheses with parameters, and a colon. The indented block beneath it is the function body.
def calculate_sum(a, b): # a and b are parameters """Return the sum of a and b.""" return a + b
result = calculate_sum(3, 1) # 3 and 1 are argumentsprint(result) # 4- Parameters are the placeholder names in the function definition.
- Arguments are the actual values passed in when the function is called.
- The docstring (a triple-quoted string on the first line) documents the function and appears in
help().
Naming and Type Hints
Section titled “Naming and Type Hints”Name functions with snake_case verbs that describe what they do (calculate_tax, send_email). Type hints document the expected types, but Python does not enforce them at runtime.
def calculate_sum(a: int, b: int) -> int: return a + bArguments
Section titled “Arguments”Positional and Keyword Arguments
Section titled “Positional and Keyword Arguments”Arguments can be passed by position or by name. Positional arguments must come first.
def describe(name, age, city): return f'{name}, {age}, {city}'
print(describe('Ann', 30, 'Paris')) # Positionalprint(describe(age=30, city='Paris', name='Ann')) # Keyword, any orderprint(describe('Ann', city='Paris', age=30)) # Mixed, positional first
describe(name='Ann', 30, 'Paris') # SyntaxError: positional argument follows keyword argumentArity Errors
Section titled “Arity Errors”A function call must supply a value for every required parameter, and no more than the function accepts.
calculate_sum() # TypeError: calculate_sum() missing 2 required positional arguments: 'a' and 'b'calculate_sum(1, 2, 3) # TypeError: calculate_sum() takes 2 positional arguments but 3 were givenDefault Parameters
Section titled “Default Parameters”A parameter with a default value becomes optional. Parameters with defaults must come after all required parameters.
def greet(name, greeting='Hello'): return f'{greeting}, {name}!'
print(greet('Alice')) # Hello, Alice!print(greet('Bob', greeting='Welcome')) # Welcome, Bob!
def broken(greeting='Hello', name): # SyntaxError: a required parameter follows a default one passArbitrary Arguments: *args and **kwargs
Section titled “Arbitrary Arguments: *args and **kwargs”When the number of arguments is not known in advance, use the unpacking operators in the definition.
*argscollects extra positional arguments into a tuple.**kwargscollects extra keyword arguments into a dictionary.
def sum_all(*args): print(args) # (1, 2, 3, 4), a tuple return sum(args)
print(sum_all(1, 2, 3, 4)) # 10
def print_profile(**kwargs): print(kwargs) # {'name': 'Alex', 'role': 'Admin'} print(type(kwargs)) # <class 'dict'>
print_profile(name='Alex', role='Admin')The same operators work at the call site to unpack a collection into arguments:
numbers = [1, 2, 3]profile = {'name': 'Alex', 'role': 'Admin'}
print(sum_all(*numbers)) # 6, same as sum_all(1, 2, 3)print_profile(**profile) # same as print_profile(name='Alex', role='Admin')Keyword-Only Parameters
Section titled “Keyword-Only Parameters”Any parameter placed after * or *args can only be passed by name. This makes calls self-documenting.
def connect(host, *, timeout=10): return f'Connecting to {host} (timeout {timeout}s)'
print(connect('example.com', timeout=5)) # Connecting to example.com (timeout 5s)print(connect('example.com', 5)) # TypeError: connect() takes 1 positional argument but 2 were givenParameter Order
Section titled “Parameter Order”When combining parameter kinds in one definition, use this order:
| Order | Kind | Example |
|---|---|---|
| 1 | Required positional | level |
| 2 | Defaults | sep=' ' |
| 3 | *args | *messages |
| 4 | Keyword-only | end='\n' |
| 5 | **kwargs | **extras |
Return Values
Section titled “Return Values”A function ends as soon as it reaches return, and the value after it is passed back to the caller.
def implicit_example(a, b): print(a + b) # Displays 4, but returns nothing
output_1 = implicit_example(3, 1)print(output_1) # None
def explicit_example(a, b): return a + b # Hands the value back to the caller
output_2 = explicit_example(3, 1)print(output_2) # 4Implicit None
Section titled “Implicit None”If a function finishes without hitting a return, it returns None NoneType. None represents “no value”, is falsy, and is a single shared object, so test for it with is None rather than ==.
print() vs. return
Section titled “print() vs. return”print() displays a value to the screen. return gives a value back to the code that called the function. Only a returned value can be used in further calculations.
def double_return(n): return n * 2
def double_print(n): print(n * 2)
print(double_return(5) + 1) # 11print(double_print(5) + 1) # Prints 10, then TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'Early Returns and Multiple Values
Section titled “Early Returns and Multiple Values”Because return exits immediately, you can use it to handle special cases first and keep the main logic flat. To return several values, separate them with commas. Python packs them into a tuple.
def classify(age): if age < 0: return 'Invalid' if age >= 18: return 'Adult' return 'Minor'
def min_max(numbers): return min(numbers), max(numbers)
low, high = min_max([3, 1, 2]) # Unpacks the returned tuple: 1 and 3How Arguments Are Passed
Section titled “How Arguments Are Passed”Python passes references to objects. What that means in practice depends on whether the object is mutable.
def add_one(items): items.append(1) # Mutates the caller's list
def reassign(items): items = [99] # Rebinds the local name only
data = []add_one(data)print(data) # [1], the caller's list was changed
reassign(data)print(data) # [1], unchangedScope defines where a variable can be seen and used. When Python meets a name, it searches four levels in order, known as the LEGB rule:
| Order | Scope | Where it lives |
|---|---|---|
| 1 | Local | Inside the current function, including its parameters |
| 2 | Enclosing | Inside any outer function that wraps the current one |
| 3 | Global | At the top level of the module |
| 4 | Built-in | Python’s own names, such as len and print |
tax_rate = 0.1 # Global variable
def calculate_tax(price): tax = price * tax_rate # price and tax are local; tax_rate is read from the global scope return tax
print(calculate_tax(50)) # 5.0print(tax_rate) # 0.1print(tax) # NameError: name 'tax' is not definedLocal variables are created when the function starts and destroyed when it ends, so they cannot be reached from outside.
Shadowing
Section titled “Shadowing”Assigning to a name inside a function creates a new local variable, even if a global with the same name exists. The global is untouched.
x = 'global'
def show(): x = 'local' print(x) # local
show()print(x) # globalThe global Keyword
Section titled “The global Keyword”Reading a global works automatically. To reassign one from inside a function, declare it with global State Mutation.
counter = 0
def broken_increment(): counter += 1 # UnboundLocalError: Python treats counter as local because it is assigned
def increment(): global counter # Bind the name to the global variable counter += 1
increment()print(counter) # 1Overusing global makes code hard to follow and test. Prefer passing values in and returning results out:
counter = 0
def increment(): global counter counter += 1
increment()def increment(counter): return counter + 1
counter = 0counter = increment(counter)The nonlocal Keyword
Section titled “The nonlocal Keyword”nonlocal does for enclosing functions what global does for the module. It is used with nested functions, commonly to keep private state between calls.
def make_counter(): count = 0
def increment(): nonlocal count # Refers to count in make_counter() count += 1 return count
return increment
counter = make_counter()print(counter()) # 1print(counter()) # 2Functions as Values
Section titled “Functions as Values”Functions are objects. You can store them in variables and pass them to other functions, which is how key= arguments work. Pass the function without parentheses: parentheses would call it immediately.
def shout(text): return text.upper()
greeter = shoutprint(greeter('hi')) # HI
words = ['banana', 'kiwi', 'apple']print(sorted(words, key=len)) # ['kiwi', 'apple', 'banana']print(sorted(words, key=lambda w: w[-1])) # ['banana', 'apple', 'kiwi'], by last letter
square = lambda n: n ** 2 # A lambda is a tiny one-expression functionprint(square(4)) # 16Designing a Good Function
Section titled “Designing a Good Function”-
Name it with a verb.
calculate_taxtells the reader what happens.taxdoes not. -
Decide the inputs. Make required values parameters, give sensible defaults to optional ones, and avoid relying on globals.
-
Do one job. If a function is hard to name or needs “and” in its description, split it into two.
-
Return results instead of printing them. Returned values can be tested, reused, and combined. Reserve
print()for functions whose purpose is output. -
Test the edge cases. Try empty input, zero, negative numbers, and the wrong type.
Common Mistakes
Section titled “Common Mistakes”| Mistake | Problem | Fix |
|---|---|---|
Calling greet instead of greet() | Without parentheses the function is not run | Add parentheses and arguments |
print() instead of return | The caller receives None | Return the value |
def f(items=[]) | The default list is shared across calls | def f(items=None) |
counter += 1 inside a function | UnboundLocalError | Use global, or better, a parameter and return value |
age = input('Age: ') then age + 1 | input() returns a string | int(input('Age: ')) |
| Required parameter after a default | SyntaxError | Put required parameters first |
| Positional argument after a keyword argument | SyntaxError | Positional first, keywords last |
| Using a local variable outside its function | NameError | Return it, or declare it at the right scope |
key=len() | Calls len with no arguments | key=len |
result = my_list.sort() | Returns None | Call my_list.sort() alone, or use sorted() |
Best Practices Checklist
Section titled “Best Practices Checklist”- Give each function a single, clear purpose and a descriptive verb name.
- Add a docstring, and type hints where they help readers.
- Return values rather than printing them.
- Never use mutable objects as default arguments.
- Pass data in through parameters instead of reading or writing globals.
- Use keyword-only parameters (after
*) for options that should be named at the call site. - Use
*argsand**kwargssparingly, and only when the function genuinely needs flexible arity. - Test for
Nonewithis None.