Python中的函数和异常

130 阅读14分钟

函数定义

基本形式

在Python中,函数是一组可重复使用的代码块,它可以接受输入参数,处理这些参数并通过返回值输出结果。定义一个函数使用关键字 def,后面跟着函数名、括号和冒号。函数体包含在缩进块中

#函数的基础定义
def say_hello():
    print(f"Hello World")

#调用函数,结构为:函数名(参数)
#输出结果为:Hello World
say_hello()

注意:

  1. 函数的参数如果不需要,可以省略
  2. 函数的返回值如果不需要,可以省略
  3. 函数需要先定义,后使用

函数的参数

  1. 位置参数(Positional Arguments):这是最常见的参数类型,按照顺序传递给函数。函数在定义时需要指定参数的名称,并在调用时按照相同的顺序传递参数值
def greet(name:str,age:int):
    print(f"姓名为:{name},年纪为:{age}")
#姓名为:小明,年纪为:22
greet("小明",22)
  1. 关键字参数(Keyword Arguments):使用参数名和对应的值来传递参数,不需要按照顺序。可以通过参数名直接传递参数值
def greet(name:str,age:int):
    print(f"姓名为:{name},年纪为:{age}")
#姓名为:小明,年纪为:15
greet(age=15,name="小明")
  1. 默认参数(Default Arguments):在函数定义时,可以为参数指定默认值。如果在函数调用时没有传递该参数的值,则会使用默认值
def greet(name:str,age = 30):
    print(f"姓名为:{name},年纪为:{age}")
#姓名为:小明,年纪为:30
greet(name="小明")
  1. 可变数量参数(Variable-Length Arguments):有时候我们不确定需要传递多少个参数给函数,可以使用可变数量参数。有两种类型的可变数量参数:*args 和 **kwargs

*args:表示接受任意数量的位置参数,以元组的形式传递

def greet(*names): 
for name in names: 
    print("Hello", name) 
#Hello Alice
#Hello Bob
greet("Alice", "Bob")

**kwargs:表示接受任意数量的关键字参数,以字典的形式传递

def greet(**person): 
for key, value in person.items(): 
    print(key, ":", value) greet(name="Alice", age=25)
#name : Alice
#age : 25
greet(name="Alice", age=25)
  1. 函数作为参数传递 在Python中,函数可以像其他数据类型一样被传递作为参数。这种特性使得函数可以作为其他函数的参数,从而实现更加灵活和抽象的功能
def add(x, y):
    return x + y

def subtract(x, y):
    return x - y

def apply_operation(func, x, y):
    return func(x, y)

result1 = apply_operation(add, 5, 3)
result2 = apply_operation(subtract, 5, 3)

print(result1)  # 输出:8
print(result2)  # 输出:2
  1. 在上面的示例中,我们定义了两个函数 addsubtract,分别用于执行加法和减法操作。然后我们定义了一个名为 apply_operation 的函数,它接受一个函数作为参数,以及两个操作数,并调用传入的函数来执行相应的操作。
  2. 当我们调用 apply_operation(add, 5, 3) 时,实际上是将函数 add 作为参数传递给 apply_operation 函数,然后在 apply_operation 函数内部调用了传入的函数 add 来执行加法操作。
  3. 这种方式使得我们可以编写通用的函数,接受不同的函数作为参数,从而实现各种不同的功能,提高代码的复用性和灵活性。
  4. 除了直接将函数作为参数传递外,还可以使用lambda表达式来传递匿名函数,从而更加简洁地完成一些操作。这种方式在需要传递简单函数时非常方便。 总之,函数作为参数的传递使得Python具有更强大的高阶函数特性,可以更加灵活地处理函数和实现各种功能

函数的返回值

函数的单一返回值

def add_twoNumber(a:int,b:int) -> int:
    result= a + b
    return result
#调用两个数相加的函数输出的结果为:8
print(f"调用两个数相加的函数输出的结果为:{add_twoNumber(3,5)}")

如果函数没有显式地使用 return 语句返回值,它将默认返回 None

def print_hello():
   print("Hello!")  
result = print_hello()
#Hello! 
#None
print(result)
  1. 在上面的示例中,函数 print_hello 打印一条消息,但没有使用 return 语句返回值。因此,在函数调用时,变量 result 被赋值为 None
  2. 注意,当函数执行到 return 语句时,函数的执行将立即结束,并且返回指定的值。如果有多个 return 语句,只有第一个被执行到的 return 语句会生效。 函数的返回值可以被赋给一个变量,也可以直接使用或传递给其他函数进行进一步的处理

函数的多个返回值

在Python中,函数可以返回多个值,这被称为多返回值。 要实现多返回值,可以使用元组(tuple)或列表(list)来存储多个返回值,并在函数的返回语句中返回这个元组或列表

def calculate_circle(radius):
    pi = 3.14159
    area = pi * radius ** 2
    circumference = 2 * pi * radius
    return area, circumference

result = calculate_circle(5)
#(78.53975, 31.4159)
print(result)

如果希望将返回的多个值分别赋值给不同的变量,可以使用类似的方式来接收多个返回值

def calculate_circle(radius):
    pi = 3.14159
    area = pi * radius ** 2
    circumference = 2 * pi * radius
    return area, circumference

area, circumference = calculate_circle(5)
#78.53975
print(area)
#31.4159
print(circumference)

需要注意的是,返回多个值时,实际上是将多个值作为一个元组或列表返回。因此,在使用时可以通过索引或迭代来访问和处理这些返回值

函数的说明文档

def greet(name:str):
    """
    打印问候语和给定名称
    :param name:要打印问候的名称
    :return:无
    """
    print(f"Hello,{name}")
  1. 函数的作用和功能描述。
  2. 参数列表,包括每个参数的名称、类型和描述。
  3. 返回值说明,描述函数的返回值内容和意义

函数的嵌套调用

在一个函数里面调用了另一个函数

def greet(name):
    print("Hello,", name, "!")
def welcome():
    greet("Alice")
    print("Welcome to our program!")
#Hello, Alice ! 
#Welcome to our program!
welcome()

在上面的示例中,greet() 函数用于打印问候语,接收一个名称作为参数。welcome() 函数调用了 greet() 函数,并在其后输出一条欢迎消息。

通过嵌套调用函数,我们可以将复杂的问题分解成更小的子问题,并用不同的函数来解决它们。这样可以提高代码的可读性、重用性和维护性 除了直接嵌套调用函数,还可以在函数调用的参数中嵌套调用其他函数

def add_numbers(x, y):
    return x + y

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

result = multiply_numbers(add_numbers(2, 3), 4)
#20
print(result)

在上面的示例中,我们先调用 add_numbers(2, 3) 来计算两个数的和(结果为5),然后将该结果作为参数传递给 multiply_numbers() 函数来计算两个数的乘积(结果为20)

变量在函数中的作用域

局部变量

  1. 在Python中,局部变量是指定义在函数内部的变量。局部变量只能在其所在的函数内部被访问和使用,在函数外部无法直接访问。
  2. 局部变量一般用于临时存储数据,只在函数内部使用,并且函数执行完毕后就会被销毁。局部变量的作用域只限于其所在的函数内部,超出该范围就无法使用该变量
def calculate_area(radius):
    pi = 3.14159 # 定义局部变量 pi
    area = pi * radius ** 2 # 计算圆的面积
    return area
    
result = calculate_area(5)
#78.53975
print(result)

在上面的示例中,我们定义了一个 calculate_area() 函数,该函数接收一个半径参数,定义了一个局部变量 pi,用于存储圆周率的值,并用这个局部变量和传入的半径计算圆的面积。最后返回计算结果。 需要注意的是,局部变量的作用域仅限于其所在的函数内部。如果在函数外部尝试访问局部变量,将会引发 NameError 错误

def calculate_area(radius):
    pi = 3.14159 # 定义局部变量 pi
    area = pi * radius ** 2 # 计算圆的面积
    return area

result = calculate_area(5)
#78.53975
print(result)
#NameError: name 'pi' is not defined
print(pi)

全局变量

  1. 在Python中,全局变量是指定义在模块级别的变量,可以在整个程序中被访问和使用。全局变量在程序的任何地方都可以被引用,包括函数内部。
  2. 在Python中,如果在函数内部没有定义与全局变量同名的局部变量,那么函数内部可以直接访问和修改全局变量的值。然而,如果在函数内部对全局变量进行赋值操作,Python会创建一个新的局部变量,并不会修改全局变量的值,如果需要在函数内部修改全局变量,可以使用 global 关键字进行声明
pi=4.14
def calculate_area(radius):
    global pi
    pi = 3.14
    area = pi * radius ** 2 # 计算圆的面积
    return area

result = calculate_area(5)
#78.5
print(result)
#3.14
print(pi)

lambda匿名函数

ambda函数是Python中的一种匿名函数,也称为匿名函数或者内联函数。它是一种简洁的函数定义方式,可以在不使用 def 关键字和函数名的情况下创建一个函数。 lambda函数的语法形式如下

lambda 参数列表: 表达式

示例演示

# 使用lambda函数计算两个数的和
add = lambda x, y: x + y
result = add(3, 5)
print(result)  # 输出:8

# 使用lambda函数判断一个数是否为偶数
is_even = lambda x: x % 2 == 0
print(is_even(4))  # 输出:True
print(is_even(5))  # 输出:False

# 使用lambda函数对列表进行排序
numbers = [5, 2, 8, 1, 6]
sorted_numbers = sorted(numbers, key=lambda x: x)
print(sorted_numbers)  # 输出:[1, 2, 5, 6, 8]
  1. 第一个lambda函数 add 接受两个参数 xy,返回它们的和。我们通过 add(3, 5) 调用lambda函数,并将结果赋值给 result 变量,然后打印出来。
  2. 第二个lambda函数 is_even 接受一个参数 x,并返回一个布尔值,表示该数是否为偶数。我们通过调用 is_even 函数,并传入不同的参数进行测试,打印出结果。
  3. 第三个lambda函数用于对列表进行排序。我们通过 sorted() 函数对 numbers 列表进行排序,使用lambda函数作为 key 参数,指定按照元素的大小进行排序。 lambda函数常用在需要定义简单函数或者作为其他高阶函数的参数时使用,可以减少代码的冗余,使代码更加简洁和易读。但请注意,在复杂的逻辑和大型函数中,建议使用常规的 def 语句来定义函数,以提高可读性和可维护性

异常

异常的概念

在 Python 中,异常是指程序运行时遇到的错误或异常情况,如除数为零、文件不存在等。当程序运行到出现异常的代码时,程序会抛出(raise)一个异常,如果没有被处理,程序就会终止并输出相关的错误信息。

Python 提供了一些内置的异常类型,如:

  • ValueError:传入无效的参数
  • TypeError:传入的参数类型不正确
  • IndexError:索引超出序列范围
  • KeyError:字典中不存在该键值
  • IOError:I/O 操作失败
  • ZeroDivisionError:除数为零等

异常的捕获

在 Python 中,使用 try...except 语句可以捕获异常并进行相应的处理。try 代码块中包含可能会抛出异常的代码,而 except 代码块中包含对异常的处理方式 try...except 语句的基本语法如下:

try:
    # 可能会抛出异常的代码
except 异常类型1:
    # 处理异常类型1的代码
except 异常类型2:
    # 处理异常类型2的代码
...
except:
    # 处理其他所有异常的代码

在上面的代码中,可以根据需要指定多个 except 代码块来处理不同类型的异常,也可以使用一个 except 代码块来处理所有的异常类型

try:
    x = int(input("请输入一个整数:"))
    y = 10 / x
    print("结果为:", y)
except ValueError:
    print("输入的不是整数!")
except ZeroDivisionError:
    print("除数不能为零!")
except Exception as e:
    print("发生了未知异常:", e)
  1. 如果用户输入的是非整数,程序就会抛出 ValueError 异常并执行 except ValueError 代码块中的语句;
  2. 如果用户输入的是零,程序就会抛出 ZeroDivisionError 异常并执行 except ZeroDivisionError 代码块中的语句;
  3. 如果程序抛出其他异常,就会执行 except Exception 代码块中的语句。可以通过 as 关键字将异常对象赋值给一个变量,以便在 except 代码块中使用

除了指定特定的异常类型外,还可以使用 elsefinally 代码块来进一步处理异常。else 代码块中的代码在没有异常发生时执行,而 finally 代码块中的代码无论是否发生异常都会执行

try:
    # 可能会抛出异常的代码
except 异常类型:
    # 处理异常的代码
else:
    # 没有异常发生时执行的代码
finally:
    # 不管是否发生异常都会执行的代码

异常的传递性

  1. 在 Python 中,异常可以被传递到调用栈的上层,直到找到能够处理该异常的代码为止。这个过程称为异常的传递性。
  2. 当发生异常时,如果当前代码块无法处理该异常,它会将异常传递给调用它的代码。如果调用代码也无法处理该异常,那么异常会继续向上传递,直到找到能够处理异常的代码或者到达程序的最顶层,如果都没有找到能够处理异常的代码,程序就会终止并打印出相应的错误信息
def divide(x, y):
    try:
        result = x / y
        print("结果为:", result)
    except ZeroDivisionError:
        print("除数不能为零!")

def calculate():
    try:
        divide(10, 0)
    except Exception as e:
        print("发生了异常:", e)
#除数不能为零! 
#发生了异常: division by zero
calculate()

divide 函数中进行除法计算时,如果除数为零,它会抛出 ZeroDivisionError 异常。但是 divide 函数没有处理该异常,所以异常被传递到调用 divide 函数的 calculate 函数中。在 calculate 函数中,通过使用 try...except 语句来捕获异常并进行处理 从输出结果可以看出,异常在 divide 函数中抛出后被传递到了 calculate 函数中,并被 calculate 函数的 except 代码块捕获并进行处理。

异常的传递性可以让我们在不同层次的代码中捕获和处理异常,从而提高程序的健壮性和容错性

练习

题目一

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点(19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode))

image.png python版:

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
    """
    :param self:是指向当前对象的引用,代表类实例自身
    :param head:是一个链表的头节点,它的类型是 ListNode 或者是 None(空节点)
    :param n: 是一个整数,表示要删除从末尾数第 n 个节点
    :return:表示该方法的返回类型是一个可选的(可以为 None)ListNode 类型
    """
    len = 0
    node = head
    while node:
        len += 1
        node = node.next
    dummy_head = ListNode(next=head)
    node = dummy_head
    while len > n:
        len -= 1
        node = node.next
    node.next = node.next.next
    return dummy_head.next

java版:

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        int len=getLength(head);
        ListNode dummyHead=new ListNode(0,head);
        ListNode node=dummyHead;
        for(int i=0;i<len-n;i++){
            node=node.next;
        }
        node.next=node.next.next;
        return dummyHead.next;
    }

    public int getLength(ListNode head){
        int len=0;
        while(head!=null){
            head=head.next;
            ++len;
        }
        return len;
    }
}

题目二(15. 三数之和 - 力扣(LeetCode))

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

注意: 答案中不可以包含重复的三元组。

image.png python版:

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        nums.sort()
        result=[]
        for i in range(len(nums)):
            # 如果第一个元素已经大于0,不需要进一步检查
            if nums[i] > 0:
                return result
            # 跳过相同的元素以避免重复    
            if i > 0 and nums[i] == nums[i-1]:
                continue
            left = i + 1
            right = len(nums) - 1
            while left < right:
                sum = nums[i] + nums[left] + nums[right]
                if sum<0:
                    left+=1
                elif sum>0:
                    right-=1
                else:
                    result.append([nums[i],nums[left],nums[right]])
                    while left<right and nums[left]==nums[left+1]:
                         left+=1
                    while left<right and nums[right]==nums[right-1]: 
                         right-=1        
                    left+=1
                    right-=1
        return result  

java版:

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        int len=nums.length;
        List<List<Integer>> list=new ArrayList<>();
        for(int i=0;i<len;i++){
            if(nums[i] > 0){
                break;
            }
            if(i>0 && nums[i] == nums[i-1]){
                continue;
            }
            int left=i+1,right=len-1;
            while(left < right){
                int sum=nums[i]+nums[left]+nums[right];
                if(sum<0){
                    ++left;
                }else if(sum > 0){
                    --right;
                }else{
                    list.add(Arrays.asList(nums[i],nums[left],nums[right]));
                    while(left<right && nums[left]==nums[left+1]) ++left;
                    while(left<right && nums[right]==nums[right-1]) --right;
                    ++left;
                    --right;
                }
            }
        }
        return list;
    }
}