4 Functions

(CSE331) Python for Data Science

Author
Affiliation

Md Rasel Biswas

IASDS, University of Dhaka

A function groups a reusable calculation or task under a name. Good functions have a clear purpose, explicit inputs, and predictable results.

1 Defining and Calling a Function

Use def, a parameter list, and an indented body. Defining a function does not execute its body; calling it does.

def greeting(name):
    print(f"Hello, {name}!")

greeting("Ayesha")
Hello, Ayesha!

name is a parameter in the definition; "Ayesha" is an argument supplied in the call.

The same function can be called with another argument:

greeting("Imran")
Hello, Imran!

2 Returning and Printing

print() displays something. return sends a value back to the caller and immediately ends that function call.

def square(n):
    return n**2

result = square(6)
print(result)
print(result + 10)
36
46

The returned value can be stored and used in another calculation.

Compare a function that only prints:

def show_square(n):
    print(n**2)

result = show_square(6)  # displays 36
print(result)           # None
36
None

A function that reaches its end without an explicit returned value returns None. For a reusable calculation, return the result and let the caller decide how to display it.

3 Default Parameter Values

Sometimes, it is useful to give a default value to a parameter. This means that if the caller does not provide a value, Python will automatically use the default.

Example:

def sum_first(arg_list, n=5):
    total = 0
    for value in arg_list[:n]:
        total += value
    return total
  • Assume n is a nonnegative integer.
  • If n is not specified, the function sums up to the first 5 elements.
  • If n is provided, it overrides the default.
  • Slicing allows shorter lists: all available elements are used. An empty list gives a sum of zero.
print(sum_first([1, 2, 3, 4, 5, 6, 7]))    # default n=5 gives 15
print(sum_first([1, 2, 3, 4, 5, 6, 7], 3)) # n=3 gives 6
15
6

3.1 Positional and keyword arguments

An argument can be matched by its position or by the parameter name:

values = [1, 2, 3, 4, 5, 6, 7]
print(sum_first(values, 3))      # positional argument
print(sum_first(values, n=3))   # keyword argument; same result
6
6

Keyword arguments make calls easier to read, especially in library functions. In these ordinary calls, positional arguments come before keyword arguments. Do not supply the same parameter twice.

4 Returning Multiple Values

To return several results together, place them in a tuple (or list). The function returns that one object, and the caller can unpack its contents.

Example:

def power(a, b):
    return (a**b, b**a)

a_to_b, b_to_a = power(2, 5)
print(a_to_b)
print(b_to_a)
32
25
  • The function returns two values: 2**5 and 5**2.
  • These are stored in two separate variables.

5 Anonymous (Lambda) Functions

  • Python has support for so-called anonymous or lambda functions, which are a way of writing functions consisting of a single expression, whose value is returned automatically.

  • They are defined with the lambda keyword:

def short_function(x):
    return x * 2
# Equivalent Anonymous Function
equiv_anon = lambda x: x * 2
equiv_anon(10)
20
  • They are especially convenient in data analysis because, as we’ll see, there are many cases where data transformation functions will take functions as arguments.

  • For example, suppose we wanted to sort a collection of strings by the number of distinct letters in each string:

strings = ["foo", "card", "bar", "aaaa", "abab"]
  • Here, we could pass a lambda function to the list’s sort method:
strings.sort(key=lambda x: len(set(x)))
strings
['aaaa', 'foo', 'abab', 'bar', 'card']

6 Generator Functions

Sometimes we want a function to produce a sequence of values one at a time instead of returning everything at once. This is where generator functions come in.

  • Defined with def, but use yield to produce individual values. Calling a generator function creates a generator; its body starts running when we request values.
  • Each time the function yields a value, its state is saved so it can resume where it left off.

Example:

def count_up_to(n):
    i = 1
    while i <= n:
        yield i   # produce a value and pause
        i += 1

gen = count_up_to(5)
print(next(gen))  # 1
print(next(gen))  # 2
1
2
  • The function doesn’t finish after the first yield.
  • Instead, it pauses and continues from where it left off when next() is called again.

After the first two calls to next(), a loop continues from the remaining values:

for value in gen:
    print(value)  # 3, 4, 5
print(list(gen))  # []: the generator is exhausted
3
4
5
[]

Create a new generator to start again. A further next() call on an exhausted generator raises StopIteration.

Why Generators?

  • Memory-efficient: They don’t store all values at once.
  • Lazy evaluation: Values are produced only when needed.
  • Useful for large datasets or infinite sequences.

Example: Infinite Even Numbers

def infinite_even_numbers():
    n = 0
    while True:
        yield n
        n += 2

gen = infinite_even_numbers()
print(next(gen))  # 0
print(next(gen))  # 2
print(next(gen))  # 4
0
2
4

7 Exercises

  1. Division with Remainder Write div_w_remainder(num, div) for integer inputs, assuming div > 0. Return the floor quotient (//) and remainder (%) as a tuple. Both results are already integers; no conversion is needed.

  2. Locating Elements in a List Write a function locate(x, item) that takes a list x and searches for the element item. Return the index of the first match, or -1 if not found. Use a loop over range(len(x)); do not use .index().

  3. Area of a Circle Write circle_area(radius, pi=3.1416) to return the area for a nonnegative radius. Call it using the default, then supply a different approximation of pi by keyword.

  4. Maximum of Three Numbers Write a function max_of_three(a, b, c) that returns the largest of the three numbers without using max(). Check negative numbers and ties.

  5. Generator Exercise Write a generator function squares(n) that yields the squares of numbers from 1 up to n. For n >= 1, test it using next() followed by a for loop. Explain why the loop starts with the next remaining value.

8 Summary

  • Define a function with def, then call it with suitable arguments.
  • print() displays a result; return makes a result available to the caller.
  • Defaults provide values for omitted arguments; keyword arguments identify parameters by name.
  • Return a tuple when several results belong together.
  • A lambda defines a short function from one expression.
  • A generator uses yield to produce values one at a time and is consumed during iteration.

9 Further reading