A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

1. if语句之外的else块else除了和if搭配之外,在Python中,它还能与for,while和 try搭配:
  • for:仅当for循环运行完毕时才运行else块
  • while:仅当while循环因为条件为假而退出时才运行else块
  • try:仅当try块中没有抛出异常时才运行else块,且else块中抛出的异常不会被前面的except子句处理
  • 在上述三个情况中,如果异常、return、break或continue语句导致控制权跳到了复合语句的主块之外,else子句会被跳过。
在这些语句中使用else字块有事能让代码更易读,而且能省去一些麻烦,不用设置控制标志或者添加额外的if语句,尤其是在和try复合时。try块中的代码应该只含有预计会抛出异常的语句,以下是两种写法的对比:
# 代码1.1,只有dangerous_all()可能会抛出异常# 写法1try:    dangerous_all()    after_call()except OSError:    log("OSError...")# 写法2,此写法比上述写法更明确try:    dangerous_all()except OSError:    log("OSError...")else:   # 但其实这么写也是多余的    after_call()复制代码但是,并不建议大家在这些关键字后面加else块,因为这很容易造成歧异,比如笔者第一眼看到for/else时的理解是:如果不能进入for块,则运行else中的内容,但实际刚好相反。在其他语言中,此时的else一般由关键字then代替,但Python的创建人非常讨厌添加新关键字,所以让else担起了这个职责。许多编程规范的书中也不建议在这些关键字后面添加else块。
***补充:***在Python中,try/except不仅用于错误处理,还和if/else一样,常用于控制流程,因此,这就形成了两种代码风格:
  • EAFP:“取得原谅比获得许可更容易”(Easier to Ask for Forgiveness than Permission),通俗讲就是“不管会不会抛异常,先运行再说,等抛出了异常再处理”,这种风格的特点就是代码中有很多try/except块;
  • LBYL:“三思而后行”(Look Before You Leap),这种风格就是显式测试前提条件,通俗讲就是“必须合规后才能运行”,这种风格的特点就是代码中有很多if/else块。
2. 上下文管理器和with块说到上下文管理器,那首先就得说说什么是上下文。笔者第一次接触这个概念的时候很费解,笔者是按语文里的概念来理解的:不就是前一句话后一句话,前一段话后一段话吗,这有什么可管理的?虽然至今笔者也没看到关于“上下文”这个概念的准确定义,但用多了之后,大致能理解为:
某段代码B将整个程序分成了3段,从前到后分别为A,B,C。当运行代码段B时,程序运行环境的某些设定需要发生改变;当退出代码段B后,这些被改变的设置需恢复原样,即保持A和C的一致性。A和B,B和C就称之为上下文。由于某些原因(如程序员大意、抛出异常强制退出等),B中所改变的设置并不总能手动恢复回去,所以,通常将这些设置交由某些对象统一管理,这些对象就叫做上下文管理器
2.1 Python中的上下文管理器上下文管理器采用的是鸭子类型技术,实现了__enter__和__exit__两个抽象方法的对象就是上下文管理器。
上下文管理器对象的存在目的是为了管理with语句,而with语句的目的是简化try/finally模式。
with块的经典用法之一就是读写文件:
# 代码2.1>>> with open("text.txt") as fp:  # 变量fp还有一个称呼,叫"句柄"...      pass...>>> fp<_io.TextIOWrapper name="text.txt" mode="r" encoding="UTF-8">复制代码解释
  • with后面的表达式(不包括as部分)得到的结果就是一个上下文管理器。此处open()函数返回了一个TextIOWrapper对象,Python解释器会临时保存这个对象,我们这里将其取名为a;
  • 在with语句块中,Python得到上下文管理器后会首先调用它的__enter__方法,如果with后面跟了as关键字,则该方法的返回值会赋给as后面的变量。上述代码中,当Python得到了a后,调用它的__enter__方法,该方法返回a对象自身(return self),然后变量fp接收这个值。但请注意,并不是所有的上下文管理器的__enter__都返回实例自身
  • 当退出with块时,Python会调用上下文管理器的__exit__方法,做最后处理。上述代码中,Python并不是调用fp.__exit__(),而是调用a.__exit__();
  • 与函数和模块不同,with块没有定义新的作用域,所以即便退出了with块,变量fp依然存在。
2.2 自定义上下文管理器下面我们自定义一个上下文管理器来说明上述四条解释:
# 代码2.2class LookingGlass:    def __enter__(self):  # 该方法只要self一个参数        import sys        self.original_write = sys.stdout.write  # 保存原方法        sys.stdout.write = self.reverse_write   # 猴子补丁,临时替换原本的方法        return "JABBERWOCKY"  # 并不一定是返回self!    def reverse_write(self, text):        self.original_write(text[::-1])   # 反转text内容    def __exit__(self, exc_type, exc_val, exc_tb):  # 该方法有4个参数!        import sys  # 由于Python会缓存导入的模块,重复导入不会消耗很多资源        sys.stdout.write = self.original_write  # 恢复到原本的方法        if exc_type is ZeroDivisionError:            print("Please DO NOT divide by zero!")            return True   # 返回True,表示异常已经正常处理# 控制台中运行>>> from mirror import LookingGlass>>> with LookingGlass() as what:...     print("Alice, Kitty and SnowDrop")...     print(what)...    porDwonS dna yttiK ,ecilAYKCOWREBBAJ>>> what'JABBERWOCKY'>>> print("Back to normal!")Back to normal!复制代码解释:
  • __enter__方法只有一个参数,即隐式的self;
  • __exit__有四个参数,第一个参数是self,其余三个参数主要用于处理with块运行期间发生的异常,分别是:

    • exc_type:异常
    • exc_val:异常实例,with块中发生异常时抛出的对象。如果__exit__想要向上抛出异常,那么在创建异常对象时传入的某些参数可从exc_val.args中获取,比如错误信息。
    • exc_tb:traceback对象。
    如果with块中没有抛出异常,Python调用__exit__方法时传入的参数是三个None,否则传入异常数据。
  • 当with块中发生异常时:如果__exit__返回True,表示异常已正确处理,Python解释器会压制异常;如果返回的是其它值,with块中的任何异常都会向上冒泡。如果with块中没有发生异常,则不用关注__exit__的返回值。

2.3 contextlib模块该模块包含了很多管理上下文的使用工具,下面列举出5个:
  • closing:如果对象提供了close()方法,但没有实现__enter__/__exit__协议,则可以使用这个函数构建上下文管理器
  • suppress:构建临时忽略指定异常的上下文管理器
  • @contextmanager:这个装饰器很常用,它把简单的生成器函数变成上下文管理器,这样就不用创建类去实现管理器协议
  • ContextDecorator:这是个基类,用于定义基于类的上下文管理器。这种上下文管理器也能用于装饰函数,在受管理的上下文中运行整个函数
  • ExitStack:这个上下文管理器能保存多个上下文管理器。它是一个栈,with结束时,依次调用栈中各个上下文管理器的__exit__方法。如果事先不知道with块要进入多少个上下文管理器,可以使用这个类。例如,同时打开任意一个文件列表中的所有文件。
2.4 @contextmanager@contextmanager装饰器能减少创建上下文管理器的样板代码量,不用编写一个完整的类,然后再实现__enter__和__exit__方法,而是只需实现一个仅含单个yield语句的生成器,生成想让__enter__方法返回的值。
在使用@contextmanager装饰的生成器中,yield语句的作用是把函数的定义体分成两部分:yield语句前面的所有代码在with块开始时(即解释器调用__enter__方法时)执行,yield之后的代码在with块结束时(即调用__exit__方法时)执行。
下面我们将之前的LookingGlass类改写为生成器版本:
# 代码2.3from contextlib import contextmanager@contextmanagerdef looking_glass():    import sys    original_write = sys.stdout.write    def reverse_write(text):        original_write(text[::-1])    sys.stdout.write = reverse_write    msg = ""    try:        yield "JABBERWOCKY"  # 如果有异常,会在这里抛出    except ZeroDivisionError:          # 该装饰器默认所有异常都得到了处理,如果不想异常被压制,请在此处抛出        msg = "Please DO NOT divide by zero!"    finally:        sys.stdout.write = original_write        if msg:            print(msg)# 用法和之前的版本一样:>>> with looking_glass() as what:   # 这里是唯一的变化...     print("Alice, Kitty and SnowDrop")...     print(what)...    porDwonS dna yttiK ,ecilAYKCOWREBBAJ>>> what'JABBERWOCKY'复制代码contextlib.contextmanager装饰器会把函数包装成实现了__enter__和__exit__方法的类。
这个类的__enter__方法有如下作用:
  • 调用生成器函数,保存生成器对象(这里称其为gen)
  • 调用next(gen),执行到yield关键字所在的位置
  • 返回next(gen)生成的值,将其绑定到with/as语句中的目标变量上
它的__exit__方法有如下作用:
  • 检查有没有把异常传给exc_type;如果有,调用gen.throw(exception),在生成器函数定义体中yield所在行抛出异常
  • 否则,调用next(gen),将生成器函数中剩余代码执行完。
前面说到,对一般的上下文管理器,如果with中抛出了异常,Python解释器会根据__exit__的返回值来决定是否压制异常。但@contextmanager则不同:它提供的__exit__方法默认所有异常都得到了处理。如果不想让@contextmanager,必须在被装饰的函数中显式重新抛出异常。
3. 总结本篇分为了两个部分,首先介绍了else与for、while以及try的搭配用法(但并不建议这么做,只需要知道能这么用就行了);随后是上下文管理器的内容,介绍了什么是“上下文”,什么是“上下文管理器”,Python中的上下文管理器以及with块,然后我们自定义了一个上下文管理器,最后介绍了contextlib模块,并用其中的@contextmanager装饰器改写了自定义的上下文管理器。


作者:VPointer
链接:https://juejin.im/post/5b220d07e51d45589479f5b3



5 个回复

倒序浏览
奈斯
回复 使用道具 举报
回复 使用道具 举报
回复 使用道具 举报
回复 使用道具 举报
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马