随着程序规模逐渐增大我们会遇到三个问题程序运行出错怎么办、不同文件中的代码如何重复使用以及如何用代码描述现实中的事物。Python 分别提供了异常处理、模块与包、面向对象编程来解决这些问题。本文将从初学者角度介绍这些重要知识。一、什么是异常程序运行过程中出现的错误通常称为异常。例如number int(input(请输入一个数字)) print(10 / number)这段代码可能出现两种异常输入Python时无法转换为整数会出现ValueError输入0时除数不能为零会出现ZeroDivisionError。出现没有处理的异常后Python 会停止运行并输出错误信息ZeroDivisionError: division by zero异常信息可以帮助我们找到错误的位置和原因。异常并不完全等于程序中的 bug例如文件不存在、用户输入错误和网络中断都可能产生异常。我们的目标不是让程序永远不出现异常而是提前考虑可能发生的问题并进行合理处理。二、捕获异常Python 使用try-except捕获异常try: 可能出现异常的代码 except: 出现异常后执行的代码例如try: number int(input(请输入一个数字)) result 10 / number print(result) except: print(输入错误请输入一个非零整数)程序会先执行try中的代码。如果没有异常就跳过except如果出现异常就停止执行剩余的try代码转而执行except。虽然直接写except:可以捕获许多异常但它捕获的范围太大可能把真正的程序错误隐藏起来。实际开发中更推荐捕获明确的异常类型。三、捕获指定异常可以在except后面写出需要处理的异常类型try: number int(input(请输入一个数字)) result 10 / number print(result) except ValueError: print(输入内容不能转换为整数) except ZeroDivisionError: print(除数不能为零)这样不同异常可以给出不同提示。还可以使用as获取具体的异常信息try: number int(Python) except ValueError as error: print(f转换失败{error})如果多个异常使用相同的处理方式可以把它们放进元组try: number int(input(请输入数字)) print(10 / number) except (ValueError, ZeroDivisionError) as error: print(f程序出现异常{error})如果确实需要捕获大多数常见程序异常可以使用try: 可能出错的代码 except Exception as error: print(f出现错误{error})Exception是大部分常见异常的父类但最好仍然优先捕获具体异常以便针对性处理。四、异常中的 else 和 finally异常处理还可以使用elsetry: number int(input(请输入数字)) result 10 / number except (ValueError, ZeroDivisionError) as error: print(f计算失败{error}) else: print(f计算成功结果是{result})else中的代码只会在没有出现异常时执行。finally中的代码无论是否出现异常都会执行file None try: file open(data.txt, r, encodingutf-8) print(file.read()) except FileNotFoundError: print(文件不存在) finally: if file is not None: file.close() print(文件处理结束)finally经常用于关闭文件、释放数据库连接或者清理资源。完整结构如下try: 可能发生异常的代码 except Exception as error: 发生异常时执行 else: 没有异常时执行 finally: 无论是否发生异常都执行这些部分不一定每次都要全部使用可以根据实际需求选择。五、异常的传递一个函数调用另一个函数时如果内层函数出现异常但没有处理异常会向外层传递。def function_b(): number int(Python) def function_a(): function_b() function_a()异常首先出现在function_b()因为它没有捕获异常所以异常会传给function_a()。function_a()仍然没有处理最后异常会传到主程序程序停止运行。我们可以在外层统一捕获def function_b(): number int(Python) def function_a(): function_b() try: function_a() except ValueError as error: print(f程序处理失败{error})异常传递使我们不必在每个小函数中重复编写处理代码。一般来说底层函数负责完成具体任务上层函数负责根据业务需要决定如何处理错误。例如读取文件的函数可以把异常继续传递主程序再决定是提示用户、重新输入还是结束程序。六、认识 Python 模块随着代码越来越多把所有内容写在一个文件中会难以维护。Python 可以把代码拆分到不同的.py文件中每个 Python 文件都可以看作一个模块。假设存在一个my_math.py文件def add(a, b): return a b def subtract(a, b): return a - b pi 3.14在另一个 Python 文件中可以导入并使用import my_math print(my_math.add(10, 20)) print(my_math.pi)模块的主要作用是组织代码和重复使用功能。Python 自带了很多模块例如import random import time import math使用随机数模块import random number random.randint(1, 10) print(number)七、模块的多种导入方式1. 导入整个模块import math print(math.sqrt(16))这种方式需要使用“模块名.功能名”来源非常清楚。2. 导入指定功能from math import sqrt print(sqrt(16))此时可以直接使用sqrt()。还可以一次导入多个内容from math import sqrt, pi print(sqrt(25)) print(pi)3. 使用别名模块名称较长时可以设置别名import numpy as np也可以给函数设置别名from math import sqrt as square_root print(square_root(16))4. 导入全部内容from math import *这种方式虽然方便但不推荐经常使用。因为我们很难看出某个变量或函数来自哪个模块而且多个模块存在同名功能时后导入的内容可能覆盖前面的内容。八、自定义模块的注意事项自定义模块就是自己编写的.py文件。假设student_tools.py中有一个函数def print_student(name, age): print(f姓名{name}年龄{age}) print_student(测试用户, 18)当其他文件导入这个模块时最后一行测试代码也会自动执行import student_tools为了让测试代码只在直接运行模块时执行可以这样写def print_student(name, age): print(f姓名{name}年龄{age}) if __name__ __main__: print_student(测试用户, 18)当直接运行student_tools.py时__name__的值是__main__测试代码会执行当它被其他文件导入时测试代码不会执行。另外自定义模块不要与 Python 内置模块重名。例如自己创建一个random.py就可能影响原来的随机数模块导入。九、Python 包当模块数量继续增加时可以使用包来管理多个模块。包本质上是一个文件夹传统结构中通常包含__init__.pymy_package/ __init__.py student.py teacher.py假设student.py中定义了函数def show_student(): print(这是学生模块)可以通过下面的方式导入import my_package.student my_package.student.show_student()也可以这样写from my_package import student student.show_student()或者直接导入具体功能from my_package.student import show_student show_student()包负责管理多个模块模块负责管理函数、类和变量。它们之间的关系可以简单理解为包 ├── 模块 │ ├── 函数 │ ├── 类 │ └── 变量十、init.py 与all__init__.py可以是空文件也可以在包被导入时执行一些初始化操作。如果希望控制下面这种写法导入哪些模块from my_package import *可以在__init__.py中定义__all____all__ [student, teacher]这样使用from my_package import *时只会导入指定模块。不过即使有__all__实际项目中仍然建议明确写出需要导入的内容。明确导入更容易阅读也能减少命名冲突。除了自己编写的包我们还会使用第三方包。例如numpy pandas matplotlib requests第三方包通常可以通过pip安装pip install requests安装后即可导入import requests十一、面向对象编程在现实世界中我们会接触学生、手机、汽车和商品等事物。每种事物都有自己的数据和行为。例如一个学生具有姓名年龄成绩学习和自我介绍等行为。面向对象编程就是尝试用程序表示这些事物。其核心概念包括类和对象。类是对一类事物的统一描述对象是根据类创建出来的具体事物。例如“学生”是一个类“小明”则是一个具体的学生对象。十二、定义类与创建对象Python 使用class关键字定义类class Student: name None age None def study(self): print(学生正在学习)创建对象时使用“类名加小括号”student1 Student()随后可以设置对象属性并调用方法student1.name 小明 student1.age 18 print(student1.name) print(student1.age) student1.study()类中的变量叫作属性或成员变量类中的函数叫作方法或成员方法。可以简单理解为类 属性 方法属性描述对象拥有什么数据方法描述对象能够做什么事情。十三、认识 self定义成员方法时第一个参数通常必须写成selfclass Student: name None def introduce(self): print(f大家好我叫{self.name})调用时不需要手动传入selfstudent1 Student() student1.name 小明 student1.introduce()执行student1.introduce()时Python 会自动把student1传给self。因此self表示当前调用这个方法的对象。不同对象拥有各自的数据student1 Student() student1.name 小明 student2 Student() student2.name 小红 student1.introduce() student2.introduce()同一个类可以创建多个对象每个对象的属性可以不同但它们都能够使用类中定义的方法。不要在定义成员方法时忘记selfclass Student: def study(): # 错误 print(正在学习)正确写法是class Student: def study(self): print(正在学习)十四、综合案例学生管理下面通过一个简单案例综合使用类、模块思想和异常处理class Student: def set_information(self, name, age, score): self.name name self.age age self.score score def introduce(self): print(f我叫{self.name}今年{self.age}岁) def show_result(self): if self.score 60: print(f成绩为{self.score}分考试及格) else: print(f成绩为{self.score}分考试不及格) try: name input(请输入学生姓名) age int(input(请输入学生年龄)) score float(input(请输入学生成绩)) student Student() student.set_information(name, age, score) student.introduce() student.show_result() except ValueError: print(年龄和成绩必须输入数字)这个程序使用Student类统一管理学生的数据和行为。当用户输入错误时使用异常处理给出提示避免程序直接崩溃。后续还可以把Student类保存到独立的模块中再在主程序里导入使用。十五、总结今天学习了异常、模块、包和面向对象基础。异常处理可以让程序在出现问题时进行补救。try中编写可能出错的代码except负责处理异常else在没有异常时执行finally则无论是否出现异常都会执行。模块是一个 Python 文件包是管理多个模块的文件夹。通过import可以重复使用其他文件中的函数、变量和类。if __name__ __main__可以避免模块被导入时自动执行测试代码。面向对象编程通过类描述一类事物通过对象表示具体事物。类中可以定义属性和方法self表示当前对象。掌握这些知识后可以尝试完成几个练习捕获用户输入错误和除零异常把计算器函数放进自定义模块创建包含多个模块的 Python 包定义一个商品类保存名称、价格和库存定义一个汽车类并实现启动和停车方法。从今天开始我们编写的程序将不再只是简单的代码片段而是逐渐拥有错误处理能力和更加清晰的项目结构。