Python Functions Tutorial - def, return, scope, keyword arguments | Beginner #11
Video: Python Functions Tutorial - def, return, scope, keyword arguments | Beginner #11 by Taught by Celeste AI - AI Coding Coach
Python Functions: def, return, scope, keyword arguments
def name(params):defines.return valuereturns. Default parameters with=. Keyword arguments at the call site. Docstrings between triple-quotes are the standard documentation.
Functions package up logic for reuse. Python's function syntax is more flexible than most languages — defaults, keyword args, multiple returns.
def: defining a function
def greet():
print("Hello, World!")
greet()
greet()
def name(): creates a function. The body is indented. Call with name().
For functions with arguments:
def greet_name(name):
print(f"Hello, {name}!")
greet_name("Alice")
greet_name("Bob")
return value
def add(a, b):
return a + b
result = add(3, 5)
print(f"3 + 5 = {result}") # 8
print(f"10 + 20 = {add(10, 20)}")
return value exits the function and returns value. Without return, the function returns None.
For early exit:
def safe_divide(a, b):
if b == 0:
return None
return a / b
Multiple return statements are fine.
Multiple return values
def divmod_op(a, b):
return a // b, a % b # implicitly a tuple
q, r = divmod_op(17, 5)
# q = 3, r = 2
Return a tuple; caller unpacks. Standard Python idiom.
Default arguments
def greet(name, greeting="Hello"):
print(f"{greeting}, {name}!")
greet("Alice") # Hello, Alice!
greet("Alice", "Hi") # Hi, Alice!
greet("Alice", greeting="Hey") # Hey, Alice! (keyword)
name=value in the parameter list provides a default. Caller can override or skip.
Defaults must come after non-defaults:
def f(a, b=2): pass # OK
def f(a=1, b): pass # SyntaxError
Mutable default trap
def append_to(item, lst=[]):
lst.append(item)
return lst
print(append_to(1)) # [1]
print(append_to(2)) # [1, 2] — same list reused!
The default [] is created once, when the function is defined. Subsequent calls share it.
The fix: use None:
def append_to(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst
Standard pattern. Always avoid mutable defaults.
Keyword arguments
def make_person(name, age, city="Unknown"):
return {"name": name, "age": age, "city": city}
# Positional
make_person("Alice", 30)
# Keyword
make_person(name="Alice", age=30, city="Portland")
# Mixed
make_person("Alice", age=30, city="Portland")
At the call site, name=value is keyword-style — order doesn't matter, only the name.
For functions with many parameters, keyword args make calls self-documenting.
Docstrings
def area(width, height):
"""Calculate area of a rectangle.
Args:
width: The width.
height: The height.
Returns:
The area as int or float.
"""
return width * height
print(area.__doc__) # the docstring
help(area) # also shows it
A string as the first statement in a function is its docstring. Tools like help(), IDE tooltips, and pydoc use it.
Conventions: Google style, NumPy style, reST. Pick one for your project.
Scope: local, enclosing, global, builtin
x = 10 # global
def outer():
y = 20 # enclosing
def inner():
z = 30 # local
print(x, y, z)
inner()
outer()
# 10 20 30
Python's scope is LEGB:
- Local — inside the current function.
- Enclosing — outer function (for nested functions).
- Global — module top-level.
- Builtin — print, len, etc.
Names are looked up from L outward.
global and nonlocal
counter = 0
def increment():
global counter # without this, counter = is a local assignment
counter += 1
increment()
increment()
print(counter) # 2
global x says "x refers to the global, not a new local."
For nested functions:
def outer():
x = 0
def inner():
nonlocal x # refers to outer's x
x += 1
inner()
print(x) # 1
nonlocal for the enclosing function's local. global for module-level.
Both are usually code smells — pure functions are easier to reason about.
A grade calculator
def calculate_average(scores):
return sum(scores) / len(scores)
def get_letter_grade(average):
if average >= 90: return "A"
elif average >= 80: return "B"
elif average >= 70: return "C"
elif average >= 60: return "D"
else: return "F"
scores = [85, 90, 78, 92, 88]
avg = calculate_average(scores)
grade = get_letter_grade(avg)
print(f"Average: {avg:.1f}, Grade: {grade}")
Two small functions, composable. Each does one thing.
Type hints (preview)
def add(a: int, b: int) -> int:
return a + b
Optional type annotations. Lesson 29 covers them.
First-class functions
def double(x): return x * 2
def triple(x): return x * 3
operations = [double, triple]
for op in operations:
print(op(5)) # 10, then 15
Functions are values — store in lists, dicts, pass as arguments. Lambda functions (lesson 13) and decorators (lesson 23) build on this.
Common stumbles
Mutable default argument. def f(lst=[]) — same list reused. Use None.
Missing return. Function returns None silently. Make sure to return when you need a value.
Indentation errors. Mix of tabs and spaces. Pick one.
Forgetting parens at call. greet without () is the function object, not the call.
Modifying global without global keyword. Local assignment shadows. Use global x to modify.
Too many positional args. For functions with > 3 args, prefer keyword for clarity.
What's next
Lesson 12: ** args and kwargs*. Variable-arity functions; the foundation for decorators and flexible APIs.
Recap
def name(params): body. return value for output (default None). Default args: def f(x, y=10); avoid mutable defaults. Keyword args at call site: f(x=1, y=2). Docstrings: triple-quoted string as first statement. Scope: LEGB (Local, Enclosing, Global, Builtin); global/nonlocal to assign upward. Functions are first-class — store, pass, return.
Next lesson: args and *kwargs.