Python 3 Fundamentals Week 16 - Functions 上 課程筆記

Introduction

Why Use Functions in Python?

  • Functions facilitate easy code re-use.
  • They help break up complex code into easier-to-understand chunks.

Parameter vs Argument

  • Parameter: What is declared in the function definition.
  • Argument: What is passed when calling the function.

Functions as Python Objects

  • Functions possess state (name, code, parameters, etc.).
  • They are callable and always return something when called.
  • Functions can be assigned to a symbol.
  • They can be passed as parameters to other functions.
  • Functions can be returned from a function call.

Callables

  • An object is callable if it can be invoked using ().
  • Functions are called by invoking them, but other object types can also be callable.
# Calling Functions
print('hello')
math.sqrt(4)

# Calling a method on the object
my_list.copy()

# Calling the object itself - callable object 
range(100)

Custom Functions

Functions are defined using the def keyword.

def function_name():
    # Indented block
    ...
    return <value>
  • The def keyword signifies the definition of a function.
  • The function’s name can be any valid Python identifier.
  • The function body contains any valid Python code.
  • Functions always return a value, defaulting to None if not specified.

Example Without Return Value

def say_hello():
    print('Hello!')

result = say_hello()  
print(result)

# Output:
# Hello!
# None

Example With Return Value

def one():
    return 1

result = one() 
print(result)

# Output:
# 1

Passing Values to Functions (by Position)

def add(a, b):  # Specify the parameters
    print(f'a = {a}')
    print(f'b = {b}')
    return a + b

add(2, 3)

# Output:
# a = 2
# b = 3
# 5

Positional arguments are passed in the order of parameters.

Namespaces

A namespace is a mapping from names to objects. Most namespaces are currently implemented as Python dictionaries. – Python glossary

A namespace is created when a function is called, and it only lasts until the function returns.

Coding

Passing values as named arguments

def gen_matrix(rows, cols, default_value):
    return [[default_value for i in range(cols)] for j in range(rows)]

gen_matrix(cols=2, rows=2, default_value=1)

# Output:
# [[1, 1], [1, 1]]

Passing values with a mix of positional and named arguments

def gen_matrix(rows, cols, default_value):
    return [[default_value for i in range(cols)] for j in range(rows)]

gen_matrix(2, cols=2, default_value=1)

# Output:
# [[1, 1], [1, 1]]

An error occurs due to multiple values assigned to the same argument:

def gen_matrix(rows, cols, default_value):
    return [[default_value for i in range(cols)] for j in range(rows)]

gen_matrix(1, rows=2, default_value=0)

Output:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[11], line 4
      1 def gen_matrix(rows, cols, default_value):
      2     return [[default_value for i in range(cols)] for j in range(rows)]
----> 4 gen_matrix(1, rows=2, default_value=0)

TypeError: gen_matrix() got multiple values for argument 'rows'

Once passing value starts using named arguments in a call, all subsequent arguments must be named too:

def gen_matrix(rows, cols, default_value):
    return [[default_value for i in range(cols)] for j in range(rows)]

gen_matrix(2, rows=2, 0)

Output:

  Cell In[15], line 4
    gen_matrix(2, rows=2, 0)
                           ^
SyntaxError: positional argument follows keyword argument

Star Arguments

When defining a function, the * can be used to capture an unlimited number of positional arguments given to the function. These arguments are captured into a tuple.

def average(*values):
    print(type(values))
    print(values)

average(1, 2, 3)

# Output:
# <class 'tuple'>
# (1, 2, 3)

Using *args for an extra argument + variable number of arguments

def my_func(a, b, *others):
    print(a)
    print(b)
    print(others)

my_func(1, 2, 3, 4, 5)

# Output:
# 1
# 2
# (3, 4, 5)
def my_func(a, b, *others, c):
    print(a)
    print(b)
    print(others)
    print(c)

my_func(1, 2, 3, 4, 5)

Output:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[5], line 7
      4     print(others)
      5     print(c)
----> 7 my_func(1, 2, 3, 4, 5)

TypeError: my_func() missing 1 required keyword-only argument: 'c'
def my_func(a, b, *others, c):
    print(a)
    print(b)
    print(others)
    print(c)

my_func(1, 2, 3, 4, c=5)

# Output:
# 1
# 2
# (3, 4)
# 5

Using *args in Function Calls

When calling a function, the * can be used to unpack an iterable into the arguments in the function call.

def my_func(*numbers):
    print(numbers)
    for num in numbers:
        print(num)
    
L = [1, 2, 3]
my_func(L, 5)

# Output:
# ([1, 2, 3], 5)
# [1, 2, 3]
# 5
def my_func(*numbers):
    print(numbers)
    for num in numbers:
        print(num)
    
L = [1, 2, 3]
my_func(*L, 5)

# Output:
# (1, 2, 3, 5)
# 1
# 2
# 3
# 5
1個讚