【python】错误和异常(第三讲)

简介: assert,翻译过来是“断言”之意。assert 是一句等价于布尔真的判定,发生异常就意味着表达式为假。assert 的应用情景就有点像汉语的意思一样,当程序运行到某个节点的时候,就断定某个变量的值必然是什么,或者对象必然拥有某个属性等,简单说就是断定什么东西必然是什么,如果不是,就抛出错误。......

🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅

✒️个人主页:小鹏linux

💊个人社区:小鹏linux(个人社区)欢迎您的加入!

目录

1. 错误和异常

1.1 assert

2. 装饰器(扩展)

2.1 Class

2.2 参数

2.3 嵌套

2.4 functools.wraps

2.5 装饰器都能干嘛?

👑👑👑结束语👑👑👑


1. 错误和异常

按照一般的学习思路,掌握了前两节内容,已经足够编程所需了。但是,我还想再多一步,还是因为本教程的读者是要 from beginner to master。

1.1 assert

>>> assert 1==1
>>> assert 1==0
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
AssertionError

image.gif

从上面的举例中可以基本了解了 assert 的特点。

assert,翻译过来是“断言”之意。assert 是一句等价于布尔真的判定,发生异常就意味着表达式为假。

assert 的应用情景就有点像汉语的意思一样,当程序运行到某个节点的时候,就断定某个变量的值必然是什

么,或者对象必然拥有某个属性等,简单说就是断定什么东西必然是什么,如果不是,就抛出错误。

#!/usr/bin/env Python
# coding=utf-8
class Account(object):
    def __init__(self, number):
        self.number = number
        self.balance = 0
    def deposit(self, amount):
        assert amount > 0
        self.balance += balance
    def withdraw(self, amount):
        assert amount > 0
        if amount <= self.balance:
            self.balance -= amount
        else:
            print "balance is not enough."

image.gif

上面的程序中,deposit() 和 withdraw() 方法的参数 amount 值必须是大于零的,这里就用断言,如果不满足条件就会报错。比如这样来运行:

if __name__ == "__main__":
    a = Account(1000)
    a.deposit(-10)

image.gif

出现的结果是:

$ python 21801.py
Traceback (most recent call last):
    File "21801.py", line 22, in <module>
        a.deposit(-10)
    File "21801.py", line 10, in deposit
        assert amount > 0
AssertionError

image.gif

这就是断言 assert 的引用。什么是使用断言的最佳时机?有文章做了总结:

如果没有特别的目的,断言应该用于如下情况:

        • 防御性的编程

        • 运行时对程序逻辑的检测

        • 合约性检查(比如前置条件,后置条件)

        • 程序中的常量

        • 检查文档

不论是否理解,可以先看看,请牢记,在具体开发过程中,有时间就回来看看本教程,不断加深对这些概念的理解,这也是 master 的成就之法。

最后,引用危机百科中对“异常处理”词条的说明,作为对“错误和异常”部分的总结(有所删改):

异常处理,是编程语言或计算机硬件里的一种机制,用于处理软件或信息系统中出现的异常状况(即超出程序正常执行流程的某些特殊条件)。

各种编程语言在处理异常方面具有非常显著的不同点(错误检测与异常处理区别在于:错误检测是在正常的程序流中,处理不可预见问题的代码,例如一个调用操作未能成功结束)。某些编程语言有这样的函数:当输入存在非法数据时不能被安全地调用,或者返回值不能与异常进行有效的区别。例如,C 语言中的 atoi 函数(ASCII串到整数的转换)在输入非法时可以返回 0。在这种情况下编程者需要另外进行错误检测(可能通过某些辅助全局变量如 C 的 errno),或进行输入检验(如通过正则表达式),或者共同使用这两种方法。

通过异常处理,我们可以对用户在程序中的非法输入进行控制和提示,以防程序崩溃。

从进程的视角,硬件中断相当于可恢复异常,虽然中断一般与程序流本身无关。

从子程序编程者的视角,异常是很有用的一种机制,用于通知外界该子程序不能正常执行。如输入的数据无效(例如除数是 0),或所需资源不可用(例如文件丢失)。如果系统没有异常机制,则编程者需要用返回值来标示发生了哪些错误。

一段代码是异常安全的,如果这段代码运行时的失败不会产生有害后果,如内存泄露、存储数据混淆、或无效的输出。

Python 语言对异常处理机制是非常普遍深入的,所以想写出不含 try, except 的程序非常困难。

2. 装饰器(扩展)

装饰器 (Decorator) 在 Python 编程中极为常⻅,可轻松实现 Metadata、Proxy、 AOP 等模式。

简单点说,装饰器通过返回包装对象实现间接调⽤,以此来插⼊额外逻辑。

语法看上去和 Java Annotation、C# Attribute 类似,但不仅仅是添加元数据。

>>> @check_args
... def test(*args):
...     print args

image.gif

还原成容易理解的⽅式:

>>> test = check_args(test)

image.gif

类似的做法,我们在使⽤ staticmethod、classmethod 时就已⻅过。

>>> def check_args(func):
...     def wrap(*args):
...         args = filter(bool, args)
...         func(*args)
... 
...     return wrap!! ! ! # 返回 wrap 函数对象
>>> @check_args! ! ! ! # 解释器执⾏ test = check_args(test)
... def test(*args):
...     print args
>>> test! ! ! ! ! # 现在 test 名字与 wrap 关联。
<function wrap at 0x108affde8>
>>> test(1, 0, 2, "", [], 3)! ! # 通过 wrap(test(args)) 完成调⽤。
(1, 2, 3)

image.gif

整个过程⾮常简单:

        • 将目标函数对象 test 作为参数传递给装饰器 check_args。

        • 装饰器返回包装函数 wrap 实现对 test 的间接调⽤。

        • 原函数名字 test 被重新关联到 wrap,所有对该名字的调⽤实际都是调⽤ wrap。

你完全可以把 "@" 当做语法糖,也可以直接使⽤函数式写法。只不过那样不便于代码维护,毕竟 AOP 极⼒避免代码侵⼊。

装饰器不⼀定⾮得是个函数返回包装对象,也可以是个类,通过 __call__ 完成目标调⽤。

>>> class CheckArgs(object):
...     def __init__(self, func):
...         self._func = func
... 
...     def __call__(self, *args):
...         args = filter(bool, args)
...         self._func(*args)
>>> @CheckArgs! ! ! ! ! ! # ⽣成 CheckArgs 实例。
... def test(*args):
...     print args
>>> test! ! ! ! ! ! ! # 名字指向该实例。
<__main__.CheckArgs object at 0x107a237d0>
>>> test(1, 0, 2, "", [], 3)! ! ! ! # 每次都是通过该实例的 __call__ 调⽤。
(1, 2, 3)

image.gif

⽤类装饰器对象实例替代原函数,以后的每次调⽤的都是该实例的 __call__ ⽅法。这种写法有点啰嗦,还得注意避免在装饰器对象上保留状态

2.1 Class

为Class 提供装饰器同样简单,⽆⾮是将类型对象做为参数⽽已。

>>> def singleton(cls):
...     def wrap(*args, **kwargs):
...         o = getattr(cls, "__instance__", None)
...         if not o:
...             o = cls(*args, **kwargs)
...             cls.__instance__ = o
... 
...         return o
... 
...     return wrap!! ! ! # 返回 wrap 函数,可以看做原 class 的⼯⼚⽅法。
>>> @singleton
... class A(object):
...     def __init__(self, x):
...         self.x = x
>>> A
<function wrap at 0x108afff50>
>>> a, b = A(1), A(2)
>>> a is b
True

image.gif

将 class A 替换成 func wrap 可能有些不好看,修改⼀下,返回 class wrap。

>>> def singleton(cls):
...     class wrap(cls):
...         def __new__(cls, *args, **kwargs):
...             o = getattr(cls, "__instance__", None)
...             if not o:
...                 o = object.__new__(cls)
...                 cls.__instance__ = o
... 
...             return o
... 
...     return wrap
>>> @singleton
... class A(object):
...     def test(self): print hex(id(self))
>>> a, b = A(), A()
>>> a is b
True
>>> a.test()
0x1091e9990

image.gif

创建继承⾃原类型的 class wrap,然后在 __new__ ⾥⾯做⼿脚就⾏了。

⼤多数时候,我们仅⽤装饰器为原类型增加⼀些额外成员,那么可直接返回原类型。

>>> def action(cls):
...     cls.mvc = staticmethod(lambda: "Action")
...     return cls
>>> @action
... class Login(object): pass
>>> Login.mvc()
'Action

image.gif

这就是典型的 metaprogramming 做法了

2.2 参数

参数让装饰器拥有变化,也更加灵活。只是需要两步才能完成:先传参数,后送类型。

>>> def table(name):
...     def _table(cls):
...         cls.__table__ = name
...         return cls
... 
...     return _table
>>> @table("t_user")
... class User(object): pass
>>> @table("t_blog")
... class Blog(object): pass
>>> User.__table__
't_user'
>>> Blog.__table__
't_blog

image.gif

只⽐⽆参数版本多了传递参数的调⽤,其他完全相同。

User = (table("t_user"))(User)

image.gif

2.3 嵌套

可以在同⼀目标上使⽤多个装饰器

>>> def A(func):
...     print "A"
...     return func
>>> def B(func):
...     print "B"
...     return func
>>> @A
... @B
... def test(): 
... print "test"
B
A

image.gif

分解⼀下,⽆⾮是函数嵌套调⽤。

test = A(B(test))

image.gif

2.4 functools.wraps

如果装饰器返回的是包装对象,那么有些东⻄必然是不同的。

>>> def check_args(func):
...     def wrap(*args):
...         return func(*filter(bool, args))
... 
...     return wrap
>>> @check_args
def test(*args):
...     """test function"""
...     print args
>>> test.__name__! ! ! # 冒牌货!
'wrap'
>>> test.__doc__! ! ! # ⼭寨货连个说明书都没有!

image.gif

⼀旦 test 的调⽤者要检查某些特殊属性,那么这个 wrap 就会暴露了。幸好有 functools.wraps。

>>> def check_args(func):
...     @functools.wraps(func)
...     def wrap(*args):
...         return func(*filter(bool, args))
... 
...     return wrap
>>> @check_args
def test(*args):
     """test function"""
     print args
>>> test
<function test at 0x108b026e0>
>>> test.__name__
'test'
>>> test.__doc__
'test function'
>>> test(1, 0, 2, "", 3)
(1, 2, 3)

image.gif

functools.wraps 是装饰器的装饰器,它的作⽤是将原函数对象的指定属性复制给包装函数对象,

默认有 __module__、__name__、__doc__,或者通过参数选择。

2.5 装饰器都能干嘛?

• AOP: ⾝份验证、参数检查、异常⽇志等等。

• Proxy: 对目标函数注⼊权限管理等。

• Context: 提供函数级别的上下⽂环境,⽐如 Synchronized(func) 同步。

• Caching: 先检查缓存是否过期,然后再决定是否调⽤目标函数。

• Metaprogramming: 这个⾃不必多说了。

• 等等……

👑👑👑结束语👑👑👑

image.gif

目录
相关文章
|
7天前
|
存储 索引 Python
|
8天前
|
Python
Python生成器、装饰器、异常
【10月更文挑战第15天】
|
5月前
|
Python
Python基础教程(第3版)中文版 第8章 异常(笔记)
Python基础教程(第3版)中文版 第8章 异常(笔记)
|
4月前
|
Unix API Python
【Python】已完美解决:(Python3.8异常)AttributeError: module ‘time‘ has no attribute ‘clock‘
【Python】已完美解决:(Python3.8异常)AttributeError: module ‘time‘ has no attribute ‘clock‘
84 0
|
19天前
|
设计模式 安全 JavaScript
Python学习八:面向对象编程(下):异常、私有等
这篇文章详细介绍了Python面向对象编程中的私有属性、私有方法、异常处理及动态添加属性和方法等关键概念。
17 1
|
2月前
|
人工智能 数据可视化 搜索推荐
Python异常模块与包
Python异常模块与包
|
27天前
|
开发者 索引 Python
Python常见的异常总结
Python 中的异常是一个非常广泛的主题,因为它包含许多内置的异常类型,这些类型可以处理各种运行时错误。
18 0
|
4月前
|
数据采集 存储 Java
如何让Python爬虫在遇到异常时继续运行
构建健壮Python爬虫涉及异常处理、代理IP和多线程。通过try/except捕获异常,保证程序在遇到问题时能继续运行。使用代理IP(如亿牛云)防止被目标网站封锁,多线程提升抓取效率。示例代码展示了如何配置代理,设置User-Agent,以及使用SQLite存储数据。通过`fetch_url`函数和`ThreadPoolExecutor`实现抓取与重试机制。
如何让Python爬虫在遇到异常时继续运行
|
4月前
|
机器学习/深度学习 运维 监控
使用Python实现深度学习模型:智能安防监控与异常检测
【7月更文挑战第26天】 使用Python实现深度学习模型:智能安防监控与异常检测
60 6
|
3月前
|
API C++ Python
【Azure 应用服务】Python fastapi Function在Azure中遇见AttributeError异常(AttributeError: 'AsgiMiddleware' object has no attribute 'handle_async')
【Azure 应用服务】Python fastapi Function在Azure中遇见AttributeError异常(AttributeError: 'AsgiMiddleware' object has no attribute 'handle_async')