MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

使用Python创建自定义函数

2021-11-082.3k 阅读

Python 自定义函数基础

在 Python 编程中,函数是组织和重用代码的关键工具。自定义函数允许我们将一组相关的语句封装在一起,以便在程序的不同地方多次调用。这不仅提高了代码的可读性和可维护性,还减少了重复代码,使程序更加简洁高效。

函数定义的基本语法

Python 中定义函数使用 def 关键字,其基本语法如下:

def function_name(parameters):
    """函数文档字符串,用于描述函数的功能、参数和返回值"""
    statements
    return expression
  • def 是定义函数的关键字,标志着函数定义的开始。
  • function_name 是你为函数取的名字,应遵循 Python 的命名规则,通常使用小写字母和下划线组合,尽量做到见名知意。
  • parameters 是函数的参数列表,可以为空,多个参数之间用逗号分隔。参数就像是函数的 “输入”,函数可以根据传入的参数执行不同的操作。
  • 函数体是缩进的代码块,包含了函数执行的具体语句。这些语句定义了函数的功能。
  • return 语句用于返回函数的执行结果。return 语句是可选的,如果没有 return 语句,函数默认返回 None

示例:简单的加法函数

下面是一个简单的加法函数示例:

def add_numbers(a, b):
    """
    这个函数接受两个数字作为参数,并返回它们的和。
    :param a: 第一个数字
    :param b: 第二个数字
    :return: a 和 b 的和
    """
    result = a + b
    return result

在上述代码中:

  • 函数名为 add_numbers,它接受两个参数 ab
  • 函数体中计算了 ab 的和,并将结果赋值给 result 变量。
  • 最后使用 return 语句返回 result

要调用这个函数,可以这样做:

sum_result = add_numbers(3, 5)
print(sum_result)  

当执行这段代码时,add_numbers 函数被调用,传入参数 35,函数计算它们的和并返回,然后将返回值赋给 sum_result 变量并打印出来,输出结果为 8

函数参数

位置参数

位置参数是最常见的参数类型,它们根据在函数定义中的位置来匹配。在前面的 add_numbers 函数中,ab 就是位置参数。当调用函数时,传递的参数必须按照函数定义中参数的顺序依次对应。例如:

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


product = multiply_numbers(4, 6)
print(product)  

这里,4 被分配给 a6 被分配给 b,函数返回它们的乘积 24。如果参数顺序颠倒,结果就会不同:

product = multiply_numbers(6, 4)
print(product)  

虽然结果在数学上相同,但这表明位置参数依赖于它们的位置。

关键字参数

关键字参数允许我们在调用函数时通过参数名来指定参数的值,而不必考虑参数的顺序。例如:

def describe_person(name, age):
    return f"{name} is {age} years old."


description1 = describe_person(name="Alice", age=30)
description2 = describe_person(age=30, name="Alice")
print(description1)  
print(description2)  

在这个例子中,nameage 是函数 describe_person 的参数。通过使用关键字参数,我们可以以任意顺序传递参数,只要参数名与函数定义中的一致即可。这使得代码更具可读性,特别是当函数有多个参数时。

默认参数

默认参数是在函数定义时为参数指定一个默认值。如果在调用函数时没有为该参数提供值,就会使用默认值。例如:

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


greeting1 = greet("Bob")
greeting2 = greet("Charlie", "Hi")
print(greeting1)  
print(greeting2)  

greet 函数中,greeting 参数有一个默认值 "Hello"。当只传递一个参数调用 greet 函数时,greeting 使用默认值;当传递两个参数时,第二个参数会覆盖默认值。

可变参数

有时候,我们不知道函数会接收到多少个参数。在这种情况下,可以使用可变参数。Python 提供了两种方式来处理可变参数:*args**kwargs

*args

*args 用于收集所有位置参数,将它们作为一个元组传递给函数。例如:

def sum_all(*args):
    total = 0
    for num in args:
        total += num
    return total


result1 = sum_all(1, 2, 3)
result2 = sum_all(10, 20, 30, 40)
print(result1)  
print(result2)  

sum_all 函数中,*args 收集了所有传入的位置参数。函数通过遍历这个元组来计算所有参数的总和。

**kwargs

**kwargs 用于收集所有关键字参数,将它们作为一个字典传递给函数。例如:

def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")


print_info(name="David", age=25, city="New York")

print_info 函数中,**kwargs 收集了所有传入的关键字参数。函数通过遍历字典的键值对来打印出每个参数及其对应的值。

函数返回值

返回单个值

大多数情况下,函数返回一个单一的值,就像前面的 add_numbersmultiply_numbers 函数一样。这种返回值可以是任何 Python 数据类型,如整数、浮点数、字符串、列表、字典等。例如:

def get_square_root(num):
    return num ** 0.5


root = get_square_root(16)
print(root)  

这里,get_square_root 函数返回一个浮点数,即输入数字的平方根。

返回多个值

Python 允许函数返回多个值。实际上,函数返回的是一个元组,只是省略了括号。例如:

def divide_numbers(a, b):
    quotient = a // b
    remainder = a % b
    return quotient, remainder


result = divide_numbers(10, 3)
print(result)  
quotient, remainder = divide_numbers(10, 3)
print(f"Quotient: {quotient}, Remainder: {remainder}")

divide_numbers 函数中,返回了商和余数。我们可以将返回值赋给一个变量,这个变量将是一个包含两个值的元组。也可以使用多个变量来分别接收返回的多个值,这种方式称为解包。

函数的嵌套定义与作用域

函数的嵌套定义

在 Python 中,函数可以在另一个函数内部定义。内部函数只能在外部函数内部被调用。例如:

def outer_function():
    def inner_function():
        print("This is the inner function.")
    inner_function()


outer_function()

在上述代码中,inner_function 定义在 outer_function 内部。只有当 outer_function 被调用时,inner_function 才会被定义并可以被调用。

作用域

作用域决定了变量在程序中的可见性和生命周期。Python 中有四种作用域:

  • 局部作用域(Local):在函数内部定义的变量具有局部作用域,只在函数内部可见。例如:
def local_scope_example():
    local_variable = 10
    print(local_variable)


local_scope_example()
# print(local_variable)  # 这将导致 NameError,因为 local_variable 在函数外部不可见
  • 嵌套作用域(Enclosing):如果一个函数定义在另一个函数内部,内部函数可以访问外部函数的变量,这些变量具有嵌套作用域。例如:
def outer():
    outer_variable = 20
    def inner():
        print(outer_variable)
    inner()


outer()

这里,inner 函数可以访问 outer 函数中的 outer_variable

  • 全局作用域(Global):在模块顶层定义的变量具有全局作用域,在整个模块内可见。例如:
global_variable = 30


def access_global():
    print(global_variable)


access_global()
print(global_variable)
  • 内置作用域(Built - in):Python 内置的函数和变量具有内置作用域,例如 printlen 等函数,在任何地方都可以直接使用。

变量作用域的规则

Python 使用 LEGB 规则来查找变量:

  1. Local:首先在函数内部查找变量。
  2. Enclosing:如果在局部作用域中没有找到,就在嵌套函数的作用域中查找。
  3. Global:如果在前两个作用域中都没有找到,就在全局作用域中查找。
  4. Built - in:如果在前三个作用域中都没有找到,就在内置作用域中查找。

例如:

global_variable = "global"


def outer():
    enclosing_variable = "enclosing"

    def inner():
        local_variable = "local"
        print(local_variable)
        print(enclosing_variable)
        print(global_variable)


    inner()


outer()

inner 函数中,按照 LEGB 规则,首先找到 local_variable,然后在嵌套作用域中找到 enclosing_variable,最后在全局作用域中找到 global_variable 并打印出来。

递归函数

递归的概念

递归是指一个函数在其定义中调用自身的过程。递归函数通常用于解决可以被分解为更小、相似子问题的问题。递归函数必须有一个终止条件,否则会导致无限递归,最终耗尽系统资源并引发错误。

示例:计算阶乘

阶乘是一个经典的递归问题。n 的阶乘(表示为 n!)定义为 n * (n - 1) * (n - 2) *... * 1。例如,5! = 5 * 4 * 3 * 2 * 1 = 120。下面是使用递归计算阶乘的 Python 代码:

def factorial(n):
    if n == 0 or n == 1:
        return 1
    else:
        return n * factorial(n - 1)


result = factorial(5)
print(result)  

在这个 factorial 函数中:

  • 终止条件是 n == 0n == 1,此时返回 1
  • 否则,函数通过调用自身 factorial(n - 1) 来计算 (n - 1) 的阶乘,并将结果乘以 n

递归的优缺点

优点

  • 递归可以使代码更加简洁和直观,特别是对于那些具有递归结构的问题,如树形结构的遍历。
  • 递归代码往往更接近数学定义或问题的自然描述,易于理解。

缺点

  • 递归可能会导致栈溢出错误。每次递归调用都会在栈中创建一个新的栈帧,如果递归深度过大,栈空间会被耗尽。
  • 递归函数的执行效率通常比迭代(使用循环)方式低,因为每次递归调用都有一定的开销,包括创建新的栈帧、传递参数等。

函数作为对象

在 Python 中,函数是一等公民,这意味着函数可以像其他数据类型一样被赋值给变量、作为参数传递给其他函数、从函数中返回以及存储在数据结构中。

函数赋值给变量

可以将函数赋值给一个变量,然后通过这个变量来调用函数。例如:

def greet():
    print("Hello!")


new_greet = greet
new_greet()

这里,greet 函数被赋值给 new_greet 变量,new_greet 现在就像 greet 一样可以被调用。

函数作为参数传递

函数可以作为参数传递给其他函数。例如,考虑一个简单的函数,它接受另一个函数作为参数并调用它:

def execute_function(func):
    func()


def print_message():
    print("This is a message.")


execute_function(print_message)

在这个例子中,print_message 函数被作为参数传递给 execute_function 函数,然后 execute_function 调用了传递进来的函数。

函数从函数中返回

函数也可以返回另一个函数。例如:

def create_adder(num):
    def adder(other_num):
        return num + other_num
    return adder


add_five = create_adder(5)
result = add_five(3)
print(result)  

create_adder 函数中,返回了内部函数 adderadd_five 是通过调用 create_adder(5) 得到的,它实际上是 adder 函数,并且已经绑定了 num 参数为 5。当调用 add_five(3) 时,就相当于调用 adder(3),其中 num5,所以返回 5 + 3 = 8

函数存储在数据结构中

可以将函数存储在列表、字典等数据结构中。例如:

def add(a, b):
    return a + b


def subtract(a, b):
    return a - b


operation_dict = {
    "add": add,
    "subtract": subtract
}


result1 = operation_dict["add"](3, 2)
result2 = operation_dict["subtract"](3, 2)
print(result1)  
print(result2)  

在这个例子中,addsubtract 函数被存储在字典 operation_dict 中。通过字典的键可以获取对应的函数并调用。

装饰器

装饰器的概念

装饰器是一种特殊的 Python 语法糖,它允许我们在不修改函数代码的情况下,为函数添加额外的功能。装饰器本质上是一个函数,它接受一个函数作为参数,并返回一个新的函数。

简单装饰器示例

下面是一个简单的装饰器示例,用于打印函数的执行时间:

import time


def timer_decorator(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"Function {func.__name__} took {end_time - start_time} seconds to execute.")
        return result
    return wrapper


@timer_decorator
def long_running_function():
    time.sleep(2)
    return "Function executed"


result = long_running_function()
print(result)

在这个例子中:

  • timer_decorator 是装饰器函数,它接受一个函数 func 作为参数。
  • wrapper 是内部函数,它在调用 func 前后记录时间,并打印出函数的执行时间。
  • @timer_decorator 是装饰器语法,它将 timer_decorator 应用到 long_running_function 上。当调用 long_running_function 时,实际上调用的是 wrapper 函数,wrapper 函数会先记录开始时间,调用原始的 long_running_function,再记录结束时间并打印执行时间,最后返回 long_running_function 的结果。

装饰器带参数

装饰器也可以接受参数。这种情况下,装饰器实际上返回一个接受函数作为参数的函数。例如,下面是一个根据参数控制是否打印日志的装饰器:

def log_decorator(log_enabled):
    def inner_decorator(func):
        def wrapper(*args, **kwargs):
            if log_enabled:
                print(f"Calling function {func.__name__}")
            result = func(*args, **kwargs)
            if log_enabled:
                print(f"Function {func.__name__} has been called")
            return result
        return wrapper
    return inner_decorator


@log_decorator(log_enabled=True)
def my_function():
    print("Inside my function")


my_function()

在这个例子中:

  • log_decorator 接受一个参数 log_enabled
  • inner_decorator 接受函数 func 作为参数,并返回 wrapper 函数。
  • wrapper 函数根据 log_enabled 的值决定是否打印日志。

装饰器在 Python 中非常有用,常用于日志记录、性能分析、权限验证等场景,它使代码的功能扩展更加简洁和优雅。

通过深入理解和掌握 Python 自定义函数的各个方面,从基础定义到高级特性,如递归、函数对象和装饰器等,开发者能够编写出更加模块化、高效且易于维护的代码,充分发挥 Python 在各种编程任务中的优势。无论是小型脚本还是大型项目,自定义函数都是 Python 编程的核心组成部分。