异常与错误处理高级用法
异常与错误处理高级用法
1.概述
这篇文章介绍如何优雅的使用异常处理好程序的错误、用更少的代码、清晰的代码,写出更健壮的程序。
2.异常高级用法
2.1.两种编程风格处理错误
使用Python处理错误有两种风格,下面介绍下这两种风格。
- LBYL(look before you leap):LBYL常备翻译成“三思而后行”,通俗讲就是在执行一个可能会出错的操作时,先做一些关键条件判断,仅当条件满足时才进行操作。
LBYL是一种本能的思考结果,他的逻辑就像“如果天气预报说今天会下雨,那么我就不出门” - EAFP(easier to ask for forgiveness than permission),可直译为“获取原谅比许可简单”。 是一种和三思而后行截然不同的编程风格,它不做任何事前检查,直接执行操作,但在外层用try来捕获异常。
这种做法类似于“出门前不看天气预报,如果下雨了就回家吃感冒药”
在python社区更偏向于使用EAFP编程风格,它的代码通常更精简。因为它不需要开发者用分支覆盖各种可能出错的情况,只需要捕获可能发生的异常情况即可。
EAFP性能更好,它直奔主要代码,省去了各种条件判断。
1.三思而后行编程风格
def incr_by_one(value):
'''
输入的参数加1返回新值
:param value:
:return:
'''
if isinstance(value, int):
return value + 1
elif isinstance(value, str) and value.isdigit():
return int(value) + 1
else:
print(f'Unable to perform incr for value: {value}')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
1.仅当类型是int类型时才执行加法操作
2.判断仅当类型时str,同时满足.isdigit() 方法时才进行操作。
这几行代码看似简单,其实代表了LBYL编程风格。
2.获取原谅比许可简单编程风格
def incr_by_try(value):
'''
输入的参数加1返回新值
:param value:
:return:
'''
try:
return int(value) + 1
except (KeyError, ValueError):
print(f'Unable to perform incr for value: {value}')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
2.2.把更精确的except语句放在前面
python内置异常类之间存在许多继承关系,举个简单的依赖关系。
BaseException —> Exception—> LookupError—> KeyError
如果一个try代码块里包含多条except,异常匹配会按照从上而下的顺序执行。假如把一个父类异常放在前面,就会导致子类异常永远不会执行。
这个示例中KeyError异常永远不会被执行,要修复这个问题就要调整except的顺序。
def incr_by_key(d, key):
try:
d[key] + 1
except Exception as e:
print(f'Unknown error: {e}')
except KeyError:
print(f'key {key} does not exists')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
2.3.try 搭配 else分支增强代码能力
用try捕获异常时,有时程序需要在一切操作没有异常后执行某个操作。为了做到这一点我们需要创建一个变量来做标记实现这个功能,如下示例。
只有当sync_profile()执行成功时,才继续调用send_notification()发送消息通知。为此我们定义一个额外变量syn_succeeded来作为标记。
def send_mess():
syn_succeeded = False
try:
sync_profile(user.profile)
syn_succeeded = True
except Exception as e:
print(f'Unknown error: {e}')
if syn_succeeded:
send_notification(user, 'profile sync succeeded')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
如果使用try搭配else分支,代码可以变得更简单。
异常捕获语句里的else表示:仅当try语句块里没有抛出任何异常时,才执行else分支下的内容,效果就像在try最后增加一个标记变量一样。
def sen_mess_try_else():
try:
sync_profile(user.profile)
syn_succeeded = True
except Exception as e:
print(f'Unknown error: {e}')
else:
send_notification(user, 'profile sync succeeded')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
2.4.使用空raise语句
在处理异常时,有时我们可能仅仅想记录下某个异常,然后把它重新抛出,交由上层处理。这是使用不带任何参数的raise语句可以派上用场。
当一个空raise出现在except时,他会原封不动的重新抛出当前异常,因此print语句不会执行。
def incr_by_raise(value):
try:
return value + 1
except TypeError:
print(f'key {value} does not exists')
raise
incr_by_raise('dd')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
2.5.抛出异常而不是返回错误
python函数支持一次返回多个值,当我们表名函数执行出错时,可以让它同时返回结果与错误信息。
下面是create_item()函数就利用了这个特性,在这段代码里,create_item()函数的功能是创建新的Item对象。当调用create_item()函数,如果执行失败函数会把错误信息放到第二个结果中返回。而当函数执行成功时,为了保持返回值统一,函数同样返回错误原因,只是内容为空字符串。
这种做法看上去很自然,但在python世界里,返回错误并非解决此类问题的最佳办法。这是因为这种做法会增加调用方处理错误的成本
MAX_LENGTH_OF_NAME = 10
MAX_ITEMS_QUOTA = 5
def cerate_item(name):
'''
接收名称,创建item对象
:param name:
:return: 返回 结果,错误信息。如果执行成功返回错误信息为空
'''
if len(name) > MAX_LENGTH_OF_NAME:
return None, 'name of item is too long'
if len(get_current_items()) > MAX_ITEMS_QUOTA:
return None, 'item is full'
# 当执行成功后返回结果和空错误信息
return Item(name), ''
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
python有完善的异常处理机制,在某种程度上鼓励我们使用异常,所以用异常来处理错误才是更地道的做法。
通过引入自定义异常类,上面的代码可以改成下面的样子
MAX_LENGTH_OF_NAME = 10
MAX_ITEMS_QUOTA = 5
Item = []
def get_current_items():
return len(Item)
# 创建自定义异常类
class CreateItemError(Exception):
pass
def create_item_except(name):
if len(name) > MAX_LENGTH_OF_NAME:
# 向上抛出异常信息
raise CreateItemError('name of item is too long')
if len(get_current_items()) > MAX_ITEMS_QUOTA:
raise CreateItemError('item is full')
return Item(name), ''
def create_from_input():
name = input()
try:
item = create_item_except(name)
except CreateItemError as e:
print(f'create item failed: {e}')
else:
print(f'item<{name}> created')
create_from_input()
#运行结果
create item failed: name of item is too long
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
用抛出异常代替返回错误后,整个代码结构看上去变化不大但细节上改变非常多。
- 新函数拥有更稳定的返回值类型,他永远只会返回Item类型或是抛出异常。
- 不同于返回值,异常在被捕获前会不断往调用栈上层汇报。因此create_item()的直接调用方可以完全不用处理CreateItemError,而交由更上层处理。异常的这个特点给了我们更多灵活性,但同时也带来了风险。假如程序缺少一个顶级的统一异常处理逻辑,那么某个被所有人忽略的异常可能会层层上报,最终弄垮整个程序。
- 虽然我们鼓励使用异常,但异常总是不可避免的然人感到“惊讶”所以最好在函数文档里说明可能抛出的异常类型。
2.6.使用上下文管理器
1.什么是上下文管理器
异常处理时,第一个想到的就是try关键字,其实除了try以外还有一个关键字和异常处理非常密切,他能简化异常处理工作,这个关键字就是with。
with是一个神奇的关键字,他可以在代码中开辟一段有他管理的上下文,并控制程序在进入和退出时的行为。
并非所有的对象都能和with配合使用,只有满足上下文管理器协议的对象才行。
上下文管理器:是一种定义了“进入”和“退出”动作的特殊对象,要创建一个上下文管理器,只要实现__enter__ 和 __exit__两个魔法方法即可。
创建一个简单的上下文管理器示例:
class DummyContext:
def __init__(self, name):
self.name = name
def __enter__(self):
# __enter__会在进入管理器时自动被调用,同时返回结果
return f'{self.name} - {random.random()}'
def __exit__(self, exc_type, exc_val, exc_tb):
# __exit__会在退出管理器时被调用
print('Exiting DummyContext')
return False
with DummyContext('foo') as name:
print(f'name: {name}')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
2.上下文管理器作用
上下文管理器作用很多,在这里可以用来简化异常处理工作。
3.替代finally语句清理资源
在编写try语句时,finally关键字经常用来做一些清理工作,比如关闭资源对象。
比如关闭网络连接对象,下面是经典的写法。
conn = create_conn(host, port, timeout=None)
try:
conn.send_text('Hello, world')
except Exception as e:
print(f'Unable to use connection:{e}')
finally:
conn.close()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
经典写法有些繁琐,使用上下文管理器可以变得简单。
# 创建上下文管理器
class create_conn_obj:
def __init__(self, host, port, timeout=None):
self.conn = create_conn(host, port, timeout=timeout)
def __enter__(self):
return self.conn
def __exit__(self, exc_type, exc_val, exc_tb):
self.conn.close()
return False
# 使用上下文管理器
with create_conn_obj(host, port, timeout=None) as conn:
try:
conn.send_text('Hellod world')
except Exception as e:
print(f'Unable to use connection:{e}')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
4.忽略异常
在执行操作时,有些程序会抛出一些不影响正常执行逻辑的异常。举个例子,当你关闭某个链接时,如果已经关闭了,解释器就会抛出Already-CloseError异常。为了程序正常运行下去,必须用try语句来捕获并忽略这个异常。
try:
conn.close()
except AlreadClosedError:
pass
- 1
- 2
- 3
- 4
虽然这样的代码很简单,当项目中有很多地方都要忽略这类异常时,这些语句就会分散在各个角落,看上去非常凌乱。
如果使用上下文管理器,可以非常方便的实现可复用的忽略异常功能。
class ignore_closed:
'''
忽略已经关闭的连接
'''
def __enter__(self):
pass
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type == AlreadyClosedError:
return True
return False
# 当你想忽略AlreadyClosedError 异常时,只要把代码用with语句包裹起来即可。
# 程序的执行结果取决于__exit__方法的返回值,如果返回了True,那么这个异常就会被当前的with语句压制住,
# 不再继续抛出达到了忽略异常效果。如果返回了False,这个异常就会被正常抛出,交调用方处理。
with ignore_closed():
close_conn(conn)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
__exit__方法三个参数介绍
- exc_type:异常的类型
- exc_val:异常对象
- exc_tb:错误的堆栈对象
如果在项目中忽略某类异常,可以直接调用标准库模块contextlib里的suppress函数,它提供了现场的忽略异常功能。
2.7.使用contextmanager装饰器
虽然上下文管理器很好用,但定义一个符合协议的管理器对象其实挺麻烦的,为了简化这部分工作,python提供了一个非常好用的工具。
@contextmanager 位于内置模块contextlib下,他可以把任何一个生成器函数直接转换为上下文管理器。
举个例子:上面实现自动关闭连接的create_conn_obj 上下文管理器,假如用函数来改写,可以简化成下面这样。
from contextlib import contextmanager
@contextmanager
def create_conn_obj(host, port, timeout=None):
'''
创建连接对象,并在退出上下文时自动关闭
:param host:
:param port:
:param timeout:
:return:
'''
conn = create_conn(host, port, timeout=timeout)
try:
yield conn
finally:
conn.close()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 以yield关键字为界,yield前的逻辑会在进入管理器时执行(类似于__enter__),yield后的逻辑会在退出管理器时执行(类似__exit__)
- 如果要在上下文管理器内处理异常,必须用try语句包裹yield语句
在日常开发中,我们用到的大多数上下文管理器,可以直接通过“生成器函数+@contextmanager”方式来定义,比创建一个符合协议的类要简单。
3.编程建议
3.1.为什么要捕获异常
在代码中捕获异常,它的核心是编码者对处于程序主流程之外的,已知或未知情况的一种妥当处置。而妥当这个词正是异常处理的关键。
异常捕获不是在拿着捕虫网玩捕虫游戏,谁捕的多就获胜。弄一个庞大的try语句,把所有可能出错、不可能出错的代码,全部用except Exception 抱起来,显然是不妥当的。
- 永远只捕获那些可能会抛出异常的语句块
- 尽量只捕获精确的异常类型,而不是模糊的Exception
- 如果出现预期外的异常,让程序早点崩溃也未必是件坏事
下面是精确捕获异常的例子
将保存文件函数代码拆分为两段更精确的异常捕获。
def save_website_title(url, filename):
# 发送请求
try:
resp = requests.get(url)
except RequestException as e:
print(f'Unable to write to file:{e}')
return False
# 获取标题
obj = re.search(r'<title>(.*)</title>, resp.text')
if not obj:
return False
title = obj.group(1)
# 保存文件
try:
with open(filename, 'w') as fp:
fp.write(title)
except IOError as e:
print(f'save filed: unable to write to write to file{filename}:{e}')
return False
else:
return True
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
3.2.异常要与当前抽象模块一致
1.为什么异常级别要与抽象模块一致
当异常抽象的级别与当前模块不一致时(异常抽象级别高于或低于当前模块),无法复用当前模块的函数。
2.异常级别与抽象模块不一致示例
当我们在写后端程序API时,通常会创建一个自定义的统一的异常类,规范API错误码,为客户端处理错误提供方便。
例如我们在项目中定义了错误码异常类:APIErrorCode,然后写了很多继承该类的错误码异常。当需要返回错误信息给调用方,只需要做一次raise就能搞定。
raise error_codes.UNABLE_TO_UPVOTE
raise error_code.USER_HAS_BEEN_BANNED
- 1
- 2
毫无疑问,大家都喜欢用这种方式来返回错误码。因为用起来非常方便:无论当前调用栈有多深,只要你想给调用方返回错误码,直接调用raise error_code.USER_HAS_BEEN_BANNED就行。
APIErrorCode异常类是整个项目中最高层的抽象之一,随着产品的不断演进,项目规模变得越来越庞大,当准备复用一个底层处理图片函数时,由于当时出于方便,在该函数里抛出了高于当前模块级别的异常抽象,打破了process_image()函数的抽象一致性,导致无法复用它。
process_image()函数会尝试打开一个文件,假如该文件不是一个有效的图片格式,就抛出error_codes.INVALID_IMAGE_UPLOADED异常,最终给用户返回错误响应码。
下面来分析下为什么不能复用函数
最初编写process_image()时,调用这个函数就只有“处理用户上传图片的POST请求” 而已。所以为了偷懒,让改函数直接抛出APIErrorCode异常类完成错误处理工作。
当我需要编写一个在后台运行的图片批处理脚本,而它刚好可以复用process_image()函数所实现的功能。
但这时事情开始白给你的不对劲起来,如果我先复用该函数,那么:
- 必须引入APIErrorCode异常类依赖来捕获异常——哪怕批处理脚本根本用不上这个异常。
- 必须捕获INVALID_IMAGE_UPLOADED异常——哪怕图片根本就不是由用户上传。
def process_image():
try:
image = Image.open(fp)
except Exception:
raise error_codes.INVALID_IMAGE_UPLOADED
- 1
- 2
- 3
- 4
- 5
3.异常与模块抽象不一致优化
这就是异常类与模块抽象级别不一致导致的结果。这类情况就是模块抛出了高于所属抽象级别的异常。避免这类错误需要注意以下两点:
- 让模块只抛出与当前级别一致的异常
- 在必要的地方进行异常包装与转换
为了满足这两点对代码进行优化
- image.processer模块应该抛出自己封装的ImageOpenError异常
- 在贴近高层抽象的地方,将图形处理模块的低级异常ImageOpenError包装为高级异常APIErrorCode
# 创建图形处理模块异常类
class ImagaOpenError(Exception):
'''图形打开错误异常类
:param exc: 原始异常
'''
def __init__(self, exc):
self.exc = exc
# 调用异常父类方法,初始化错误信息
super().__init__(f'Image open error: {self.exc}')
# 抛出自己封装的ImageOpenError异常
def process_image():
try:
image = Image.open(fp)
except Exception as e:
raise ImagaOpenError(exc=e)
# 在贴近高层抽象的地方,将图形处理模块的低级异常ImageOpenError包装为高级异常APIErrorCode
def foo_view_function(request):
try:
process_image(fp)
except ImagaOpenError:
raise raise error_codes.INVALID_IMAGE_UPLOADED
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
这样调整以后,就能在后台脚本复用process_image()函数
4.包装抽象级别低于当前模块的异常
除了应该避免抛出高于当前抽象级别的异常外,我们同样应该避免抛出低于当前抽象级别的异常。
如果你使用过第三方HTTP工具库requests,可能已经发现他在请求出错时抛出的异常,并不是他在底层所使用的urllib3模块的原始异常,而是经过requests.exceptions包装过的异常:
try:
requests.get('https://www.baidu.com')
except Exception as e:
print(type(e))
# 输出结果
<class 'requests.exceptions.ConnectionError'>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
这样做是为了保证异常类的抽象一致性
urllib3模块是requests依赖的底层实现细节,而这个细节在未来是有可能变动的。当某天requests真的要修改底层实现时,这些包装过的异常类,就可以避免对用户侧的错误处理逻辑产生不良影响。
3.3.不要随意忽略异常
假如send_sms_notification执行失败就会抛出RequestError异常,他会直接被except忽略,就好像从来没有发生过一样。
如果这个异常影响流程执行将会停止程序。
假如这个异常不会影响流程执行,我们将它输出到日志中也总比忽略好,在需要查看异常时可以通过日志查看。
try:
send_sms_notification(user, message)
except RequestError:
pass
- 1
- 2
- 3
- 4
当编码者决定让自己的代码抛出异常时,他肯定是有原因的。希望调用自己代码的人对这个异常做点什么,调用方可以做如下操作:
- 在except语句捕获并处理它,继续执行后面的代码
- 在except语句捕获它,将错误通知给终端用户,终端执行
- 不捕获异常,让异常网堆栈上层走,最终可能导致程序崩溃
3.4.抛出可区分的异常
当开发者创建自定义异常类时,需要遵循下面几条原则
- 要继承Exception而不是BaseException
- 异常类名称最好以Erro或Exception结尾
- 让调用方清晰区分各种异常
1.抛出可区分的异常实例
def create_from_input():
name = input()
try:
item = create_item(name)
except CreateItemError as e:
print(f'create item failed: {e}')
else:
print(f'item<{name}> created')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
假如示例中,调用者想针对item已满,这里错误增加一些特殊逻辑,比如情况所有items,我们就的把代码改成下面这样。虽然这段代码通过对比错误字符串实现了需求,但这种做法非常脆弱。假如create_item()稍微调整下一场信息,代码逻辑就会崩溃。
def create_from_input():
name = input()
try:
item = create_item(name)
except CreateItemError as e:
if str(e) == 'items is full':
clear_all_items()
print(f'create item failed: {e}')
else:
print(f'item<{name}> created')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
2.设计可区分的更精准的异常类
为了解决上面的问题,我们可以利用异常间的继承关系,设计一些更精准的异常子类。
这样调用方在捕获异常后,也能根据异常对象的error_code来精确分辨异常类型。
# 创建create_item异常类
class CreateItemError(Exception):
'''创建Item失败异常'''
# 继承CreateItemError异常类,创建子异常类
class CreateItemFullError(CreateItemError):
'''当前Item已满异常'''
def create_item(name):
if len(name) > MAX_LENGTH_OF_NAME:
raise CreateItemError('name of item is too long')
if len(get_current_items()) > MAX_ITEMS_QUOTA:
raise CreateItemFullError('item is full')
return Item(name=name)
# 调用create_item函数,抛出精确的异常类
def create_from_input():
name = input()
try:
item = create_item_except(name)
except CreateItemFullError as e:
clear_all_items()
print(f'create item failed:{e}')
except CreateItemError as e:
print(f'create item failed:{e}')
else:
print(f'item<{name}> created')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
3.5.空对象模式
空对象模式就是本该返回None值或抛出异常值时,返回一个符合正常结果接口的特征“空类型对象” 来代替,以此免去调用方错误处理工作。
1.空对象示例
下面是一个统计得分合格的程序,正常得分记录格式是username points 格式,但是由于格式可能不符合要求,所有make_userpoint()方法在解析数据时会抛出异常来通知调用方。
QUALIFIED_POINTS = 80
# 创建异常类
class CreateUserPointError(Exception):
'''创建用户得分记录失败时抛出异常'''
# 计算得分是否合格
class UserPoint:
def __init__(self, username, points):
self.username = username
self.points = points
def is_qualified(self):
return self.points > QUALIFIED_POINTS
def make_userpoint(point_string):
'''
从字符串中初始化一条得分记录
:param point_string:
:return:
'''
try:
username, points = point_string.split()
points = int(points)
except ValueError:
raise CreateUserPointError(f'input must follow pattern "{username} {points}"')
if points < 0:
raise CreateUserPointError('points can not be nagative')
return UserPoint(username=username, points=points)
# 计算得分合格的人数
def count_qualified(points_data):
'''
计算得分合格的总人数
:param points_data:
:return:
'''
result = 0
for point_string in points_data:
try:
point_obj = make_userpoint(point_string=point_string)
except CreateUserPointError:
pass
else:
result += point_obj.is_qualified()
return result
# 测试得分合格的人数
data = [
'zhangsan 39',
'lisi 50',
'wangwu 90',
'zhaoliu 88',
'tom'
]
print(count_qualified(points_data=data))
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
假如引入空对象模式,上面的异常处理逻辑可以完全消失。
QUALIFIED_POINTS = 80
class UserPoint:
def __init__(self, usname, points):
self.name = usname
self.points = points
def is_qualified(self):
return self.points >= QUALIFIED_POINTS
class NullUserPoint:
'''
一个空的用户得分记录
'''
username = ''
points = 0
def is_qualified(self):
return False
def make_userpoint(point_string):
'''
从字符串初始化一条得分记录
:param point_string:
:return:
'''
try:
username, points = point_string.split()
points = int(points)
except ValueError:
return NullUserPoint()
if points < 0:
return NullUserPoint()
return UserPoint(username=username, points=points)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
在新版本里,定义了一个代表“空得分记录” 的新类型:NullUserPoint,每当make_userpoint()接收到无效的输入,执行失败时,就会返回一个NullUserPoint对象。因为返回的是空对象而不是异常,所以调用者count_qualified()就不在需要处理任何异常了。
def count_qualified(points_data):
# 如果没有make_userpoint执行没有异常就会调用UserPoint对象的is_qualified,如果遇到异常就会调用NullUserPoint对象的is_qualified返回false
return sum(make_userpoint(s).is_qualified() for s in points_data)
- 1
- 2
- 3
文章来源: brucelong.blog.csdn.net,作者:Bruce小鬼,版权归原作者所有,如需转载,请联系作者。
原文链接:brucelong.blog.csdn.net/article/details/126669104
- 点赞
- 收藏
- 关注作者
评论(0)