Python 04 | 使用函数和异常处理语句串联数值模型

Introduction

上一期我们讲到使用Python中的控制流语句实现批处理与自动化,通过循环语句重复执行计算和使用条件语句区分不同条件下的执行路径,我们可以串联起一个复杂而庞大的程序。

然而,这是一种理想情况。重复循环某个代码块,意味着循环体就是我们需要执行的所有内容。

假设某一部分的计算需要在完整的程序中复用(如多个循环体功能类似),我们依然需要重复编写该部分代码,但会使得代码冗余和复杂度增加。

一般而言,当我们使用多线程或多进程时,我们可以通过灵活调度多个CPU加速计算过程,但这一过程通常是基于函数展开的,循环语句则过度依赖于单核CPU的性能。

此外,我们只用到了条件语句和循环语句,说明我们知道程序执行过程中所有可能的情况,能够确保程序运行中不会出现任何错误。但这对于过于大型的科学计算模型而言,似乎也不大可能(凡事总有例外)。

综合以上情况,编写可重复使用的代码块(即函数)和做好程序的异常处理就尤为重要。下面我们就进行一些简单的探讨。

函数

多么令人深恶痛绝的一个词,没想到在数学领域被折磨那么多年,学代码依然逃离不了它的魔爪。

但是与数学中定义不同的是,我们都知道数学中的函数是一种一一对应的映射关系,多值函数通常不认为是正统的函数。

而在程序设计中,函数的定义更加宽泛,可以是任意的输入输出映射关系。输入输出甚至不要求它是一个数值,函数体也不像数学中要求一个明确的解析式(没错,更复杂了)。

Python中定义函数只需要使用关键字def即可,通过指定函数名称和输入的参数,我们就实现了如数学中y=f(x)中的f(x)的部分(括号里的x仅作为一个实例,输入参数可以是任意个数)。

而紧跟在def后面的函数体,则是f的具体内容,即我们要怎么对输入参数进行处理。

当我们最终计算完成,获得了我们需要的y时(同理,y也可以是任意个输出值),我们就可以使用return语句返回计算结果。若计算结果本身对我们接下来的步骤没有用处,return可以不存在。

如我们已经在计算完某一模块结果,且该结果不参与后续计算,我们直接在函数中导出至本地文件,便无需使用return语句。

下面是一些简单案例:

# 加法
def add(a, b):
    return a + b

print(add(2, 3))

# 定义一个函数实现摄氏度转换为华氏度或开氏度
def celsius_to_fahrenheit_or_kelvin(celsius, to_fahrenheit=True):
    if to_fahrenheit:
        return (celsius * 9/5) + 32
    else:
        return celsius + 273.15

print(celsius_to_fahrenheit_or_kelvin(25))
print(celsius_to_fahrenheit_or_kelvin(25, False))

# 定义一个函数计算圆的面积和周长
import math

def circle_area_and_circumference(radius):
    area = math.pi * radius ** 2
    circumference = 2 * math.pi * radius
    return area, circumference

print(circle_area_and_circumference(5))

# 定义一个函数判断一个数是否为素数
def is_prime(n):
    if n <= 1:
        return False
    for i in range(2, int(math.sqrt(n)) + 1):
        if n % i == 0:
            return False
    return True

print(is_prime(1))
print(is_prime(9))
print(is_prime(17))
5
77.0
298.15
(78.53981633974483, 31.41592653589793)
False
False
True

以上几个例子中,我们可以看到Python函数非常灵活的写法及其与控制流语句间自由的组合方式。对于C语言等更传统的程序语言而言,整个程序就是一个Main函数。由于Python具有交互的特点,主函数的定义被淡化,而更类似于命令行的形式进行程序设计。

值得注意的是,函数celsius_to_fahrenheit_or_kelvin中,我们使用到了参数to_fahrenheit,该参数默认为True,即默认将摄氏度转换为华氏度。如果我们需要将摄氏度转换为开氏度,则需要传入to_fahrenheit=False参数。而当我们所需的就是转换为华氏度,由于默认值为True,则可以省略该参数。

细心的朋友应该注意到,我们在填写False转换为开氏度时,并没有使用to_fahrenheit=False的写法,而是直接传入了False值。Python中如果我们按照函数参数的顺序传入参数,则不需要写参数名,直接传入参数值即可。

此外,函数之间可以互相调用和嵌套,我们可以将各个组件封装成简单的函数,最终组合在一起,从而实现更复杂的功能。

# 计算列表每个元素的平方
def square(ls):
    return [x**2 for x in ls]

# 计算列表平方和
def sum_of_squares(ls):
    return sum(square(ls))

result = sum_of_squares([1, 2, 3, 4, 5])
print(result)
55

这里我们通过嵌套调用函数实现了列表元素平方和的计算,对于实际的地球科学应用中,如我们需要计算辐射四分量,最终获得总辐射收支。我们就可以分别定义各个辐射分量的函数,然后调用这些函数,最后计算总辐射。

上面的square函数中我们还用到了列表推导式,该表达式可以简化循环,后续我们再展开讲解。类似的情况还有条件推导式、字典推导式等。

我们可以给出一个等价案例便于大家理解:

ls = [1, 2, 3, 4, 5]

# 循环
ls0 = []
for i in ls:
    ls0.append(i ** 2)

print(ls0)

# 列表推导式
ls1 = [i ** 2 for i in ls]
print(ls1)
[1, 4, 9, 16, 25]
[1, 4, 9, 16, 25]

异常处理

当我们完成一个大型的计算任务代码的编写,我们总是期望它能适应所有使用场景。然而,天不遂人愿,总是会有这样那样的问题存在。

最常见的诸如数据类型错误、文件组织形式差异,甚至是内存溢出等,都可能导致程序运行的中断。那么是否有可能,我们先不管那些存在错误的个体,先总体运行完成获得结果,再逐一排除错误,完成所有任务呢?

答案是肯定的,Python中的异常处理机制便是针对这一问题设计的解决方案。

总体而言,异常处理机制主要通过几个关键字来实现:

  • try: 用于定义一个可能发生异常的区域,在此区域中,可以进行一些可能出现异常的操作。(简而言之,我们程序的主体应当放置于try中)

  • except: 用于处理try中可能出现的异常,如果try中的代码没有发生异常,则不执行此部分代码。(针对try中可能出现的异常进行处理,可以是跳过当前异常、打印错误信息、或是记录错误日志等)

  • else: 用于定义在try中没有发生异常时执行的代码。(即当try中代码正常运行完毕时,执行else中的代码)

  • finally: 用于定义无论是否发生异常都要执行的代码,通常用于释放资源等。(无论如何,都会执行finally中的代码)

其中,tryexcept必须成对出现以定义异常处理的区域,elsefinally则是可选的。

下面是一个简单的例子:

x = 1

try:
    x = x / 0
except ZeroDivisionError:
    print("division by zero!")
else:
    print("result is", x)
finally:
    del x
    print("clean up.")
division by zero!
clean up.

在这个例子中,我们尝试执行1/0,这是一个除以0的操作,因此会导致ZeroDivisionError异常。我们使用except语句捕获这个异常,并打印出错误信息。如果没有异常发生,则执行else语句,打印出结果。最后,finally语句清除变量用于释放资源。

但是,这里我们是能够预见到除以0的异常的,很多时候,我们并不知道会发生什么异常,因此,我们可以使用所有异常的通用写法Exception来捕获所有的异常(尽管这样做并不推荐,但如果我们都不知道会发生什么异常,就可以用这种方式查看异常的类型)。

ls = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

for i in ls:
    try:
        y = 10 / i
    except Exception as e:
        print('Error: ', str(e))
    else:
        print(f"The result of 10 / {i} is {y}")
    finally:
        print("This is the finally block")
Error:  division by zero
This is the finally block
The result of 10 / 1 is 10.0
This is the finally block
The result of 10 / 2 is 5.0
This is the finally block
The result of 10 / 3 is 3.3333333333333335
This is the finally block
The result of 10 / 4 is 2.5
This is the finally block
The result of 10 / 5 is 2.0
This is the finally block
The result of 10 / 6 is 1.6666666666666667
This is the finally block
The result of 10 / 7 is 1.4285714285714286
This is the finally block
The result of 10 / 8 is 1.25
This is the finally block
The result of 10 / 9 is 1.1111111111111112
This is the finally block

可以看到,我们使用Exception捕获到了异常,异常信息的具体内容即为除以0导致了错误。

后记

以上便是Python中函数与异常处理的基本内容,它们的重要性不言而喻。

函数在程序设计中的地位不亚于其在数学中的地位,它可以使代码更加模块化、可重用、易于维护,是编程的基石。

异常处理则是程序运行中出现错误时,程序能够自动处理并向用户反馈错误信息的一种机制。它为程序的健壮性和稳健性(鲁棒性)提供了保障,让我们的程序能应对各种异常情况。

函数和异常处理在我们编写大型程序时至关重要,当后续我们提及到多线程处理等场景时,程序主体基本就是一个函数。

学习到这里,Python基本的语法已经总体上介绍完毕,诸如class之类的高阶功能在我们编写自己的数据类型和模块时会用到,但目前的内容已经足够满足我们日常的使用。

随着各种方法、内容的累积,代码块复杂度越来越高,需要我们不断应用、总结、提炼,才能更好地理解和掌握Python。「绝知此事要躬行」,自己动手DIY实现所需的功能,能更准确地理解程序设计的本质。

那么,我们下期再见!

Manuscript: RitasCake
Proof: Philero; RitasCake

获取更多资讯,欢迎订阅微信公众号:Westerlies

跳转和鲸社区,云端运行本文案例。icon-default.png?t=N7T8https://www.heywhale.com/mw/project/66221ce2e584e69fbfef87ba

关注我们,阅读原文跳转和鲸社区

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值