内容简介:“Python有什么好学的”这句话可不是反问句,而是问句哦。主要是煎鱼觉得太多的人觉得Python的语法较为简单,写出来的代码只要符合逻辑,不需要太多的学习即可,即可从一门其他语言跳来用Python写(当然这样是好事,谁都希望入门简单)。于是我便记录一下,如果要学Python的话,到底有什么好学的。记录一下Python有什么值得学的,对比其他语言有什么特别的地方,有什么样的代码写出来更Pythonic。一路回味,一路学习。
“Python有什么好学的”这句话可不是反问句,而是问句哦。
主要是煎鱼觉得太多的人觉得 Python 的语法较为简单,写出来的代码只要符合逻辑,不需要太多的学习即可,即可从一门其他语言跳来用Python写(当然这样是好事,谁都希望入门简单)。
于是我便记录一下,如果要学Python的话,到底有什么好学的。记录一下Python有什么值得学的,对比其他语言有什么特别的地方,有什么样的代码写出来更Pythonic。一路回味,一路学习。
引上下文管理器
太极生两仪,两仪为阴阳。
道有阴阳,月有阴晴,人有生死,门有开关。
你看这个门,它能开能关,就像这个对象,它能创建能释放。(扯远了
编程这行,几十年来都绕不开内存泄露这个问题。内存泄露的根本原因,就是把某个对象创建了,但是却没有去释放它。直到程序结束前那一刻,这个未被释放的对象还一直占着内存,即使程序已经不用这个对象了。泄露的量少的话还好,量大的话就直接打满内存,然后程序就被kill了。
聪明的 程序员 经过了这十几年的努力,创造出很多高级编程语言,这些编程语言已经不再需要让程序员过度关注内存的问题了。但是在编程时,一些常见的对象释放、流关闭还是要程序员显式地写出来。
最常见的就是文件操作了。
常见的文件操作方式
原始的Python文件操作方式,很简单,也很common(也很java):
def read_file_1(): f = open('file_demo.py', 'r') try: print(f.read()) except Exception as e: pass f.close()
就是这么简简单单的,先open然后读写再close,中间读写加个异常处理。
其中close就是释放资源了,在这里如果不close,可能:
- 资源不释放,直到不可控的垃圾回收来了,甚至直到程序结束
- 中间对文件修改时,修改的信息还没来得及写入文件
- 整个代码显得不规范
因此写上close函数理论上已经必须的了,可是 xxx.close()
这样写上去,在逻辑复杂的时候让人容易遗漏,同时也显得不雅观。
这时,各种语言生态有各种解决方案。
像Java,就直接jvm+依赖注入,直接把对象的生命周期管理接管了,只留下对象的使用功能给程序员;像golang,defer一下就好。而python最常用的则是with,即上下文管理器
使用上下文管理器
用with之后的文件读写会变成:
def read_file_2(): with open('file_demo.py', 'r') as f: print(f.read())
我们看到用了with之后,代码没有了open创建,也没有了close释放。而且也没有了异常处理,这样子我们一看到代码,难免会怀疑它的健壮性。
为了更好地理解上下文管理器,我们先实现试试。
实现上下文管理器
我们先感性地对with进行猜测。
从调用with的形式上看,with像是一个函数,包裹住了open和close:
# 大概意思而已 with = open + do + close def with(): open(xxxx) doSomething(xxxx) close(xxxx)
而Python的库中已有的方案(contextmanager)也和上面的伪代码具有一定的相似性:
from contextlib import contextmanager @contextmanager def c(s): print(s + 'start') yield s print(s + 'end')
“打印start”相当于open,而“打印end”相当于close,yield语法和修饰器(@)不熟悉的同学可以复习一下这些文章:生成器和 修饰器 。
然后我们调用这个上下文管理器试试,注意煎鱼还给上下文管理器加了参数s,输出的时候会带上:
def test_context(): with c('123') as cc: print('in with') print(type(cc)) if __name__ == '__main__': test_context()
我们看到,start和end前都有实参s=123。
现实一个上下文管理器就是这么简单。
异常处理
但是我们必须要注重异常处理,假如上面的上下文管理器中抛异常了怎么办呢:
def test_context(): with c('123') as cc: print('in with') print(type(cc)) raise Exception
结果:
显然,这样弱鸡的异常处理,煎鱼时忍不了的。而且最重要的是,后面的close释放居然没有执行!
我们可以在实现上下管理器时,接入异常处理:
@contextmanager def c(): print('start') try: yield finally: print('end') def test_except(): try: with c() as cc: print('in with') raise Exception except: print('catch except')
调用test_except函数输出:
我们在上下文管理器的实现中加入了try-finally,保证出现异常的时候,上下文管理器也能执行close。同时在调用with前也加入try结构,保证整个函数的正常运行。
然而,加入了这些东西之后,整个函数变得复杂又难看。
因此,煎鱼觉得,想要代码好看,抽象的逻辑需要再次升华,即从函数的层面升为对象(类)的层面。
实现上下文管理器类
其实用类实现上下文管理器,从逻辑理解上简单了很多,而且不需要引入那一个库:
class ContextClass(object): def __init__(self, s): self.s = s def __enter__(self): print(self.s + 'call enter') return self def __exit__(self, exc_type, exc_val, exc_tb): print(self.s + 'call exit') def test(self): print(self.s + 'call test')
从代码的字面意思上,我们就能感受得出来, __enter__
即为我们理解的open函数, __exit__
就是close函数。
接下来,我们调用一下这个上下文管理器:
def test_context(): with ContextClass('123') as c: print('in with') c.test() print(type(c)) print(isinstance(c, ContextClass)) print('') c = ContextClass('123') print(type(c)) print(isinstance(c, ContextClass)) if __name__ == '__main__': test_context()
输出结果:
功能上和直接用修饰器一致,只是在实现的过程中,逻辑更清晰了。
异常处理
回到我们原来的话题:异常处理。
直接用修饰器实现的上下文管理器处理异常时可以说是很难看了,那么我们的类选手表现又如何呢?
为了方便比较,煎鱼把未进行异常处理的和已进行异常处理的一起写出来,然后煎鱼调用一个不存在的方法来抛异常:
class ContextClass(object): def __init__(self, s): self.s = s def __enter__(self): print(self.s + 'call enter') return self def __exit__(self, exc_type, exc_val, exc_tb): print(self.s + 'call exit') class ContextExceptionClass(object): def __init__(self, s): self.s = s def __enter__(self): print(self.s + 'call enter') return self def __exit__(self, exc_type, exc_val, exc_tb): print(self.s + 'call exit') return True def test_context(): with ContextExceptionClass('123') as c: print('in with') t = c.test() print(type(t)) # with ContextClass('456') as c: # print('in with') # t = c.test() # print(type(t)) if __name__ == '__main__': test_context()
输出不一样的结果:
结果发现,看了半天,两个类只有最后一句不一样:异常处理的类中 __exit__
函数多一句返回,而且还是return了True。
而且这两个类都完成了open和close两部,即使后者抛异常了。
而在 __exit__
中加 return True
的意思就是不把异常抛出。
如果想要详细地处理异常,而不是像上面治标不治本的隐藏异常,则需要在 __exit__
函数中处理异常即可,因为该函数中有着异常的信息。
不信?稍微再改改:
class ContextExceptionClass(object): def __init__(self, s): self.s = s def __enter__(self): print(self.s + 'call enter') return self def __exit__(self, exc_type, exc_val, exc_tb): print(self.s + 'call exit') print(str(exc_type) + ' ' + str(exc_val) + ' ' + str(exc_tb)) return True
输出与预期异常信息一致:
先这样吧
若有错误之处请指出,更多地请关注造壳。
以上所述就是小编给大家介绍的《《Python有什么好学的》之上下文管理器》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。