KTU S1

Defining and Using Functions in Python

By the end you should be able to: Define and call Python functions with parameters and return values, distinguish parameters from arguments, and explain local versus global scope.
def area_of_circle(radius):         # def, name, parameters, colon
    """Return the area of a circle of the given radius."""
    return 3.14159 * radius * radius


a = area_of_circle(5)               # call

Four parts: the def keyword, a name, parameters in brackets, and an indented body. The string on the first line is a docstring — optional but good practice, and it is what help(area_of_circle) shows.

Parameters and arguments

  • Parameter — the name in the definition (radius)
  • Argument — the value passed at the call (5)

Worth keeping straight because exam questions use the terms precisely.

return

return sends a value back and exits immediately:

def grade_for(marks):
    if marks >= 90:
        return "S"          # exits here; nothing below runs
    if marks >= 50:
        return "P"
    return "F"

No else is needed — reaching the second if already means the first returned nothing.

A function with no return returns None:

def show(x):
    print(x)                # prints, returns nothing

result = show(5)            # result is None

That distinction matters: print displays, return gives back. A function that prints its answer cannot have that answer used in a calculation. This is the single most common misunderstanding in this topic.

def bad_square(n):
    print(n * n)            # cannot be used further

def good_square(n):
    return n * n

total = good_square(3) + good_square(4)     # 25
total = bad_square(3) + bad_square(4)       # TypeError: None + None

Default and keyword arguments

def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

greet("Anjali")                      # 'Hello, Anjali!'
greet("Rahul", "Good morning")       # 'Good morning, Rahul!'
greet(greeting="Hi", name="Meera")   # order does not matter when named

Parameters with defaults must come after those without.

One trap worth knowing: never use a mutable default.

def add_mark(mark, marks=[]):       # WRONG
    marks.append(mark)
    return marks

add_mark(90)        # [90]
add_mark(80)        # [90, 80]  — the SAME list, still there

The default is created once when the function is defined, not on each call. Use None instead:

def add_mark(mark, marks=None):
    if marks is None:
        marks = []
    marks.append(mark)
    return marks

Scope

Names created inside a function are local — they exist only during the call:

def f():
    x = 10          # local
    return x

f()
print(x)            # NameError — x does not exist out here

A function can read a global name but not rebind it without saying so:

count = 0

def increment():
    count = count + 1       # UnboundLocalError

def increment_properly():
    global count            # explicit
    count += 1

global is usually a sign the design is wrong. Prefer passing values in and returning results out — a function whose behaviour depends only on its arguments is far easier to test and reason about.

Mutable arguments

Passing a list gives the function access to the same list, so changes are visible to the caller — the aliasing point from the lists topic:

def append_zero(items):
    items.append(0)         # modifies the caller's list

data = [1, 2]
append_zero(data)
print(data)                 # [1, 2, 0]

If you do not want that, pass a copy: append_zero(data.copy()).