错误异常处理与面向对象
1. 错误和异常
如果遇到了错误(异常),如何去处理?
1.1 基本概念
1.1.1 Python 异常
当程序运行时,因为遇到未知的错误而导致中止运行,便会出现Traceback 消息,打印异常。异常即是一个事件,该事件会在程序执行过程中发生,影响程序的正常执行。一般情况下,在Python 无法正常处理程序时就会发生一个异常。异常是Python 对象,表示一个错误。当Python 脚本发生异常时我们需要响应处理它,否则程序会终止执行。
异常 | 描述 |
---|---|
SyntaxError | 语法错误 |
NameError | 未声明/初始化对象 |
IndexError | 序列中没有此索引 |
KeyboardInterrupt | 用户中断执行(Ctrl+C) |
EOFError | 没有内建输入,到达EOF 标记(Ctrl+D) 不适用windows 系统 |
IOError | 输入/输出操作失败 |
ValueError | 当操作或函数接收到具有正确类型但值不适合的参数, 并且情况不能用更精确的异常,例如 IndexError来描述时将被引发。 |
TypeError | 字符串与整数相加时触发。 |
1.2 检测(捕获)异常
如果一个语句发生了错误或异常,跳过该语句的执行,执行另外的语句。
1.2.1 try except 语句
尝试执行try 语句,如果遇到异常则执行except 语句。两个语句执行执行一个。
语法规则
try:
pass # 监控这里的异常
except Exception[, reason]:
pass # 异常处理代码
示例:
# 01 - 异常初探.py
try:
username = input("Please input your name: ")
print(f"Welcome, {username}")
except:
print("\nSomething Error!")
1.2.2 捕获多种异常
算命脚本:输入年龄,预测明年的年龄。
可以把多个except 语句连接在一起,处理一个try 块中可能发生的多种异常。
# 02 - 捕获多种异常.py
banner = '''
算命脚本
1. 预测年龄
2. 预测财运
3. 预测姻缘
'''
print(banner)
choice = input("Please input the number: ")
choice = int(choice)
if choice != 1:
print("好好学习...")
exit()
try:
age = input("Please input your age: ")
print(f"The next year your name: {int(age) + 1}")
except ValueError:
print("Please input a number!")
except KeyboardInterrupt:
print("\nCtrl + C")
except:
print("\nSomething Error!")
1.2.3 捕获所有异常
如果出现的异常没有出现在指定要捕获的异常列表中,程序仍然会中断。可以使用在异常继承的树结构中,BaseException 是在最顶层的,所以使用它可以捕获任意类型的异常。
except BaseException: # 捕获所有异常,相当于except
print("\nSomething Error!")
Pyhton 异常树
BaseException 所有异常的基类
|
|
+-- SystemExit 解释器请求退出
|
|
+-- KeyboardInterrupt 用户中断执行(通常是输入^C)
|
|
+-- GeneratorExit 生成器调用close();方法时触发的
|
|
+-- Exception 常规错误的基类,异常都是从基类Exception继承的。
|
+-- StopIteration 迭代器没有更多的值
|
+-- StandardError 所有的内建标准异常的基类
| +-- BufferError 缓冲区操作不能执行
| +-- ArithmeticError 所有数值计算错误的基类
| | +-- FloatingPointError 浮点计算错误
| | +-- OverflowError 数值运算超出最大限制
| | +-- ZeroDivisionError 除(或取模)零 (所有数据类型)
| +-- AssertionError 断言语句失败
| +-- AttributeError 访问未知对象属性
| +-- EnvironmentError 操作系统错误的基类
| | +-- IOError 输入输出错误
| | +-- OSError 操作系统错误
| | +-- WindowsError (Windows) 系统调用失败
| | +-- VMSError (VMS) 系统调用失败
| +-- EOFError 没有内建输入,到达EOF 标记
| +-- ImportError 导入模块/对象失败
| +-- LookupError 无效数据查询的基类,键、值不存在引发的异常
| | +-- IndexError 索引超出范围
| | +-- KeyError 字典关键字不存在
| +-- MemoryError 内存溢出错误(对于Python 解释器不是致命的)
| +-- NameError 未声明/初始化对象 (没有属性)
| | +-- UnboundLocalError 访问未初始化的本地变量
| +-- ReferenceError 弱引用(Weak reference)试图访问已经垃圾回收了的对象
| +-- RuntimeError 一般的运行时错误
| | +-- NotImplementedError 尚未实现的方法
| +-- SyntaxError 语法错误
| | +-- IndentationError 缩进错误
| | +-- TabError Tab 和空格混用
| +-- SystemError 一般的解释器系统错误
| +-- TypeError 对类型无效的操作
| +-- ValueError 传入无效的参数
| +-- UnicodeError Unicode 相关的错误
| +-- UnicodeDecodeError Unicode 解码时的错误
| +-- UnicodeEncodeError Unicode 编码时错误
| +-- UnicodeTranslateError Unicode 转换时错误
|
+-- Warning 警告的基类
+-- DeprecationWarning 关于被弃用的特征的警告
+-- PendingDeprecationWarning 关于特性将会被废弃的警告
+-- RuntimeWarning 可疑的运行时行为(runtime behavior)的警告
+-- SyntaxWarning 可疑的语法的警告
+-- UserWarning 用户代码生成的警告
+-- FutureWarning 关于构造将来语义会有改变的警告
+-- ImportWarning 关于模块进口可能出现错误的警告的基类。
+-- UnicodeWarning 有关Unicode警告的基类。
+-- BytesWarning 有关字节警告相关的基类。
1.3 处理异常
try: # 尝试执行某个语句
num = int(input("The number:"))
except BaseException: # 如果遇到异常,执行的语句
print("something error!")
else: # 如果没有遇到异常,执行的语句
print(num)
finally: # 不管是否遇到异常,都要执行的语句。
print("This is finally")
- else 子句:在try 范围中没有异常被检测到时,执行else 子句。在else 范围中的任何代码运行前,try 范围中的所有代码必须完全成功。
- finally 子句:finally 子句是无论异常是否发生,是否捕捉都会执行的一段代码。比如获取用户的输入,写入文件,但是如果在获取数据过程中,发生了异常,就会导致整个脚本结束执行,文件也无法关闭了。为了解决这个问题,可以采用异常处理中的finally 子句,也就是说,无论语句是否正常执行,都需要关闭。
# 03 - 处理异常.py
banner = '''
算命脚本
1. 预测年龄
2. 预测财运
3. 预测姻缘
'''
print(banner)
choice = input("Please input the number: ")
choice = int(choice)
if choice != 1:
print("好好学习...")
exit()
try:
age = input("Please input your age: ")
age = int(age)
except ValueError:
print("Please input a number!")
except KeyboardInterrupt:
print("\nCtrl + C")
except:
print("\nSomething Error!")
else:
print(f"The next year your name: {age + 1}")
finally:
print("脚本执行结束,祝你好运!")
1.4 特殊场景
1.4.1 with 语句
with 语句是用来简化代码的。比如在将打开文件的操作放在with 语句中,代码块结束后,文件将自动关闭。用来简化文件操作的打开和关闭,其中closed 属性是判断文件是否被关闭的
>>> with open('foo.py') as f:
1.5 脚本完善
from pyfiglet import Figlet
from termcolor import colored, cprint
f = Figlet(font='slant')
print('*' * 75)
print('-' * 75)
print(f.renderText('WuHu Exception'))
banner = '''
算命脚本
1. 预测年龄
2. 预测财运
3. 预测姻缘
4. 退出脚本
'''
print(banner)
print('-' * 75)
print('*' * 75)
choice = input("请输入你的选项: ")
choice = int(choice)
if choice == 1:
try: # 尝试执行某个语句
age = int(input("Please input your age: "))
except ValueError: # 遇到异常执行的语句
cprint("\nPlease input a number!", "white", "on_red")
except KeyboardInterrupt: # 遇到异常执行的语句
cprint("\nCtrl + C", "white", "on_red")
except: # 遇到异常执行的语句
cprint("\nSomething Error!", "white", "on_red")
else: # 如果没有遇到异常,执行的语句
cprint(f"\nThe next year your age: {int(age) + 1}", "white", "on_green")
finally: # 无论是否遇到异常,都会执行的语句
cprint("\n脚本执行完毕", "white", "on_green")
elif choice == 4:
print("欢迎下次光临~")
exit()
else:
print("好好学习!")
2. 内网主机存活检测程序
2.1 scapy 模块
与scrapy 有非常大的区别。
scapy 是一个Python 的第三方模块,被称为“网络神器”。scapy 模块能够发送、捕获、分析和铸造网络数据包。
2.1.1 主要功能
2.1.2 scapy 安装
Windows 下安装scapy
python -m pip install scapy
Kali 中自带scapy 环境。
2.1.3 进入scapy 模块
┌──(ajest zh-CN)-[~]
└─$ sudo scapy
aSPY//YASa
apyyyyCY//////////YCa |
sY//////YSpcs scpCY//Pp | Welcome to Scapy
ayp ayyyyyyySCP//Pp syY//C | Version 2.4.5
AYAsAYYYYYYYY///Ps cY//S |
pCCCCY//p cSSps y//Y | https://github.com/secdev/scapy
SPPPP///a pP///AC//Y |
A//A cyP////C | Have fun!
p///Ac sC///a |
P////YCpc A//A | Craft packets before they craft
scccccp///pSP///p p//Y | you.
sY/////////y caa S//P | -- Socrate
cayCyayP//Ya pY/Ya |
sY/PsY////YCc aC//Yp
sc sccaCY//PCypaapyCP//YSs
spCPY//////YPSps
ccaacs
using IPython 7.22.0
>>>
2.1.4 简单使用
构造数据包
>>> pkt = IP()/TCP()
>>> pkt.show()
###[ IP ]###
version = 4
ihl = None
tos = 0x0
len = None
id = 1
flags =
frag = 0
ttl = 64
proto = tcp
chksum = None
src = 127.0.0.1
dst = 127.0.0.1
\options \
###[ TCP ]###
sport = ftp_data
dport = http
seq = 0
ack = 0
dataofs = None
reserved = 0
flags = S
window = 8192
chksum = None
urgptr = 0
options = ''
>>> pkt = IP(src = "192.168.1.11", dst = "192.168.1.1")/TCP()
>>> pkt.show()
###[ IP ]###
version = 4
ihl = None
tos = 0x0
len = None
id = 1
flags =
frag = 0
ttl = 64
proto = tcp
chksum = None
src = 192.168.1.11
dst = 192.168.1.1
\options \
###[ TCP ]###
sport = ftp_data
dport = http
seq = 0
ack = 0
dataofs = None
reserved = 0
flags = S
window = 8192
chksum = None
urgptr = 0
options = ''
>>>
发送数据包
发送数据包的函数 | 说明 |
---|---|
sr(pkt) | 发送数据包,接收所有返回包 |
sr1(pkt) | 发送数据包,接收一个返回包 |
send(pkt) | 发送数据包,不等待返回包 |
srp(pkt) | 发送2 层数据包,等待回应 |
sendp(pkt) | 发送2 层数据包,不等待返回包 |
>>> res = sr1(pkt)
Begin emission:
Finished sending 1 packets.
.*
Received 2 packets, got 1 answers, remaining 0 packets
>>>
解析:
-
Received 2 packets表示接收了2个包
-
got 1 answers表示响应了1个包
查看返回包
>>> res.show()
###[ IP ]###
version = 4
ihl = 5
tos = 0x0
len = 44
id = 13990
flags =
frag = 0
ttl = 255
proto = tcp
chksum = 0x46a4
src = 10.9.21.1
dst = 10.9.21.111
\options \
###[ TCP ]###
sport = http
dport = ftp_data
seq = 1510518667
ack = 1
dataofs = 6
reserved = 0
flags = SA
window = 65535
chksum = 0x4f9c
urgptr = 0
options = [('MSS', 1460)]
###[ Padding ]###
load = '\x00\x00'
>>>
2.2 主机存活检测程序
# 04 - 内网主机存活检测程序.py
from scapy.all import *
from scapy.layers.inet import *
from termcolor import colored, cprint
import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
network = "192.168.188."
for host in range(1, 20):
ip = f"{network}{host}"
# print(f"[-] Trying: {ip}")
# 创建数据包
pkt = IP(src = "192.168.188.185", dst = ip)/ICMP()
# 发送数据包
res = sr1(pkt, timeout = 0.2, verbose = False)
if res and res.type == 0:
print(colored(f"\n{ip} is ALIVE!","green"))
else:
print(f"\n{ip} is NOT ALIVE!",end= "")
解析:
-
res = sr1(pkt, timeout = 0.2, verbose = False)
:sr1()
函数将发送数据包并等待响应,然后返回第一个收到的响应数据包。如果在指定的超时时间内没有收到响应,则返回 None。并且禁用了详细输出。timeout = 0.2
:设置的超时时间为0.2verbose = False
:禁用了详细输出
-
logging:屏蔽信息模块。
-
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
:在scapy运行期间,只有ERROR才显示输出其他都不输出。
3. 面向对象编程
3.1 类
类是一类事物的统称,比如学生。对象就是类的实例化。
类有属性(变量),比如学生的姓名、性别、年龄、成绩等,也就是编程里面的变量。
类有方法(函数),比如学生的上课、下课等,也就是编程里面的函数。
3.1.1 创建类
类是一种数据结构,我们可以用它来定义对象,对象把数据值和行为特性融合在一起。
Python 使用 class
关键字来创建类:通常类名的第一个字母大写,推荐使用驼峰式命名法,单词首字母均大写。类有属性(变量)和方法(动作,函数)组成。
class ClassName(bases):
'class documentation string' #'类文档字符串'
class_suite # 类体
3.1.2 _init_ 方法
__init__()
是类的实例(对象)创建后第一个被调用的方法(自动被调用),通常被用来进行对象中属性(变量)的初始化。设置实例的属性可以在实例创建后任意时间进行,但是通常情况下优先在__init__()
方法中实现。
- 定义类型
- 实例化对象(创建一个对象)
- 初始化对象(第一次给对象属性赋值)
# 05 - __init__.py
class Stu():
def __init__(self, name, sex, age, score):
self.name = name
self.sex = sex
self.age = age
self.score = score
stu1 = Stu(name = "AJEST", sex = True, age = 24, score = 59.9)
print(f"{stu1.name} 的成绩是{stu1.score}")
解析:
-
在 Python 类中,
self
是一个约定俗成的名称也是一个特殊的参数,它表示类的实例(对象)本身。可以使用其他名称代替self
,但强烈建议仍然使用self
,这是因为self
在Python社区中被广泛接受并被视为最佳实践。在类的方法中,使用self
参数可以访问该对象的属性和方法。 -
在
__init__()
方法中,self
参数指向正在创建的对象。通过使用self
参数,我们可以将传递给__init__()
方法的参数的值分配给对象的属性,以便在整个类的其他方法中使用。 -
self.name = name
表示将传递给构造函数的name
参数的值赋给对象的name
属性。类似地,self.sex = sex
、self.age = age
和self.score = score
将传递的参数值分别赋给对象的sex
、age
和score
属性。
3.2 方法
3.2.1 绑定方法
方法仅仅是类内部定义的函数,方法只有在其所属的类拥有实例时,才能被调用;任何一个方法定义中的第一个参数都是变量self,它表示调用此方法的实例对象就是自己。
类中的方法,也就是类中定义的函数,这个函数第一个参数永远是self,表示自己。
# 06 - 绑定方法.py
import time
class Stu():
def __init__(self, name, sex, age, score):
self.name = name
self.sex = sex
self.age = age
self.score = score
def getSocre(self):
print(f"{self.name} 的成绩是{self.score}")
def goodGoodStudy(self):
print("好好学习中...")
time.sleep(10)
self.score += 0.1
stu1 = Stu(name = "AJEST", sex = True, age = 24, score = 59.9)
stu2 = Stu(name = "HMM", sex = True, age = 24, score = 89.9)
# 调用方法
stu2.getSocre()
stu2.goodGoodStudy()
stu2.getSocre()
解析:
- 在
getSocre()
方法中,我们使用self.name
来访问对象的name
属性。这里的self
指的就是调用getSocre()
方法的对象本身。
3.3 继承
3.3.1 子类继承
继承描述了基类(祖先)的属性如何遗传给派生类(子孙),子类可以继承它的基类的任何属性,不管是数据属性还是方法。
# 34 - 子类继承.py
import time
class Kid():
def __init__(self, name = "", sex = "", age = ""):
self.name = name
self.age = age
self.sex = sex
def play(self):
print("玩游戏中...")
class Stu(Kid):
def __init__(self,name = "", sex = "", age = "", score = ""):
Kid.__init__(self, name, sex, age)
self.score = score
def get_score(self):
print(f"{self.name} 的成绩是{self.score}")
def good_good_study(self):
print("好好学习中...")
time.sleep(10)
self.score += 0.1
stu1 = Stu(name = "AJEST", sex = True, age = 24, score = 59.9)
stu2 = Stu(name = "LL", sex = True, age = 25, score = 49.9)
stu3 = Stu(name = "HMM", sex = True, age = 23, score = 99.9)
stu4 = Stu(name = "LH", sex = True, age = 24, score = 79.9)
stu1.play()
3.3.2 方法重写
如果子类中有和父类同名的方法,父类方法将被覆盖;如果需要访问父类的方法,则要调用一个未绑定的父类方法,明确给出子类的实例。
# 08 - 方法重写.py
import time
class Kid():
def __init__(self, name, sex, age):
self.name = name
self.sex = sex
self.age = age
def play(self):
print("玩游戏中...")
class Stu(Kid):
def __init__(self, name, sex, age, score):
Kid.__init__(self, name, sex, age)
self.score = score
def getSocre(self):
print(f"{self.name} 的成绩是{self.score}")
def goodGoodStudy(self):
print("好好学习中...")
time.sleep(10)
self.score += 0.1
def play(self):
print("玩王者荣耀中...")
time.sleep(2)
self.score -= 10
stu1 = Stu(name = "AJEST", sex = True, age = 24, score = 59.9)
stu2 = Stu(name = "HMM", sex = True, age = 24, score = 89.9)
stu2.getSocre()
stu2.play()
stu2.getSocre()
stu2.goodGoodStudy()
stu2.goodGoodStudy()
stu2.getSocre()
stu2.play()
stu2.getSocre()
说明:
Kid.__init__(self, name, sex, age)
是在Stu
类的构造函数中调用了Kid
类的构造函数来初始化从Kid
类继承的属性。- 在Python中,当一个类继承自另一个类时,子类可以通过调用父类的构造函数来初始化继承的属性。在这个例子中,
Stu
类继承自Kid
类,因此在Stu
类的构造函数中,我们需要先调用Kid
类的构造函数来初始化继承的属性。
3.3.3 多重继承
需要注意的是,Python支持单继承和多继承。单继承指一个类只能继承自一个父类,而多继承指一个类可以同时继承自多个父类。在多继承的情况下,子类可以获得多个父类的属性和方法,但需要注意避免出现命名冲突和混淆的情况。
# 09 - 多重继承.py
class A():
def a():
print("This is function A!")
class B():
def b():
print("This is function B!")
class C(A, B):
pass
C.a()
C.b()
说明:
- 在Python中,
pass
是一个空语句,不执行任何操作。当你在代码中遇到需要写一段代码但又暂时没有实现的情况时,可以使用pass
来占位,使得代码结构完整而没有语法错误。
# 定义食物类
class Food():
def __init__(self,name,price):
self.name = name
self.price = price
# 定义蔬菜类
class Vegetable(Food):
def __init__(self,name,price,place):
Food.__init__(self,name,price)
self.place = place
# 定义肥料类
class Manure():
def __init__(self,flag):
self.flag = flag
# 定义西红柿类
class Tomato(Vegetable,Manure):
def __init__(self, name, price, place,colour,flag):
Vegetable.__init__(self, name, price, place)
Manure.__init__(self,flag)
self.colour = colour
def wuhu(self):
print(f"{self.name}价格为{self.price},产地是{self.place},颜色是{self.colour}")
print(f"添加肥料{self.flag}")
tomato = Tomato("西红柿🍅","23💴","北京","红色🔴","💩")
tomato.wuhu()
3.3.4 静态方法和动态方法的区别
静态方法和动态方法的区别:
-
静态方法:静态方法是属于类的方法,它与实例无关。它们不需要访问或修改类的实例属性,也没有
self
参数。静态方法可以被类本身直接调用,而不需要创建类的实例。静态方法通常用于执行与类关联但不依赖于实例的操作,或者在方法内部不需要访问实例属性的情况下。 -
动态方法:动态方法是属于实例的方法,它需要通过创建类的实例来调用。动态方法的第一个参数通常是
self
,它代表实例本身。通过这个参数,动态方法可以访问和修改实例的属性。动态方法定义在类的内部,可以通过实例访问,也可以通过类访问。
3.4 魔法函数
3.4.1 类和实例的内建函数
函数 | 作用 |
---|---|
issubclass() | 判断一个类是另一个类的子类或子孙类 |
isinstance() | 判定一个对象是否是另一个给定类的实例 |
hasattr() | 判断一个对象是否有一个特定的属性 |
getattr() | 获得一个对象的属性值 |
setattr() | 设置一个对象的属性 |
delattr() | 删除一个对象的属性 |
3.4.2 常用的魔法函数
魔法函数是系统自带的,会在“恰当”的时候自动调用。
# 10 - 魔术方法.py
class Test():
def __init__(self):
print("Function __init__ is called!")
def __str__(self):
return "Why print(self)?"
def __call__(self):
print("Why call me like Function?")
t = Test()
print(t)
t()
解析:
- _init_:该函数会在创建对象的时候调用。
- _str_:该函数用于返回对象的字符串表示,当尝试将对象转换为字符串时(例如通过
print()
函数或str()
函数),Python 会调用该方法来获取对象的字符串表示。 - _call_:当我们使用对象名后面加上一对小括号
()
来调用对象时,Python 会调用该方法。 - _len_:长度方法,在使用
len()
函数时自动调用,返回对象的长度。 - _getitem_:索引获取方法,在使用索引访问对象时自动调用,返回对应位置的值。
t()
:使用对象名后面加上一对小括号()
来调用对象。
3.5 私有化
Python 为类元素(属性和方法)的私有性提供初步的形式,由双下划线开始的属性在运行时被“混淆”,所以直接访问是不允许的。
通过给属性和方法名添加双下划线 __
来将它们私有化。例如,__score
表示一个私有属性,还可以__method()
表示一个私有方法。私有属性和方法只能在类定义中被引用,子类也无法访问。
import time
class Stu():
def __init__(self, name, sex, age, score):
self.name = name
self.sex = sex
self.age = age
# 定义私有属性
self.__score = score
def getSocre(self):
print(f"{self.name} 的成绩是{self.__score}")
def goodGoodStudy(self):
print("好好学习中...")
time.sleep(10)
self.__score += 0.1
# 定义私有方法
def __wuhu(self):
print("芜湖")
class R(Stu):
def __init__(self,name,sex,age,score):
Stu.__init__(self, name, sex, age,score)
def play(self):
print("玩🐍")
print(f"{self.name}的成绩是{self.__score}")
stu1 = Stu("wuhu",True,24,77)
stu1.getSocre()
stu1.goodGoodStudy()
stu1.getSocre()
r = R("haha",True,24,66)
# 调用私有方法
r.__wuhu()
# r.play()
如果Stu的子类访问的话Stu中的私有属性就会抛出一个访问未知对象属性(AttributeError)的异常。