美文网首页学习提升Python
31.3-魔术方法—上下文管理

31.3-魔术方法—上下文管理

作者: BeautifulSoulpy | 来源:发表于2019-12-29 11:27 被阅读0次

我们不能选择自己的原生家庭,但我们可以选择自己今后成为什么样的人。
人生是一场马拉松,起步很重要,但可以抵达的终点更重要。
愿我们都有勇气,穿过最深沉的夜,突出重围,寻找到属于自己的人生意义!

总结

  1. with语法在打开实例时,并不都是实例本身;在enter方法中,return self 时才是实例本身;
  2. with语法在实例化的情况下,可以执行多次(复用),用完就释放;避免占用资源;
  3. 多个装饰器执行的顺序:(自下而上);

with语法在源码中大量使用,非常重要;

1. 上下文管理

什么是上下文管理器?
我们常见的with open操作文件,就是一个上下文管理器。如:

with open(file, 'rb') as f:
    text = f.read()

上下文管理器:是指在一段代码执行之前执行一段代码,用于一些预处理工作entre;执行之后再执行一段代码,用于一些清理工作exit。

比如刚提到的文件操作,打开文件进行读写,读写完之后需要将文件关闭。很明显用到了上下文管理器。主要依靠enterexit这两个”魔术方法”实现。
与方法的实现涉及到两个魔法函数_enter和_exit

class Contextor():
    def __enter__(self):    
        print('程序的预处理开始啦!')
        return self        # 作为as说明符指定的变量的值

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('正在进行收尾!')

    def func(self):
        print('程序进行中....')


with Contextor() as var:
    var.func()

# 输出
程序的预处理开始啦!
程序进行中....
正在进行收尾!

从这个示例可以很明显的看出,在编写代码时,可以将资源的连接或者获取放在_enter中,而将资源的关闭写在_exit 中。

# 1.先enter ,最后执行exit;有raise异常都无所谓; 
import time
class Point:
    def __init__(self):
        print('init')
    def __enter__(self):
        print('enter')
    def __exit__(self,exc_type,exc_val,exc_tb):
        print('exit')
        
with Point() as f:
    print('~~~~~~~~~~~~~~~~')   # 程序开始时 先 init,enter
    time.sleep(1)
    print('!!!!!!!!!!!!!!!!!')  # 程序运行完成 时  执行 exit;
#---------------------------------------------------------------------------
init
enter
~~~~~~~~~~~~~~~~
!!!!!!!!!!!!!!!!!
exit

为什么文件对象和函数对象不相等?

# 2. 如果使用as 语法,其值为enter的返回值;
import time
class Point:
    def __init__(self):
        print('init')
        
    def __enter__(self):
        print('enter')
        return self
    
    def __exit__(self,exc_type,exc_val,exc_tb):
        print('exit')
pt = Point()
with pt as f: # 如果使用as 语法,其值为enter的返回值;
    print('~~~~~~~~~~~~~~~~')   # 程序开始时 先 init,enter
    print(f == pt)
    
f = open('test.ini')

with f as p:  # 
    print(f == p,'~~~~~~~~~')
#----------------------------------------------------------
init
enter
~~~~~~~~~~~~~~~~
True
exit
True ~~~~~~~~~

exc_type,exc_val,exc_tb

在 写_exit_ 函数时,需要注意的事,它必须要有这三个参数
•exc_type:异常类型
•exc_val:异常值
•exc_tb:异常的错误栈信息

当主逻辑代码没有报异常时,这三个参数将都为None。

# 3. 错误参数:exc_type,exc_val,exc_tb 
import time
class Point:
    def __init__(self):
        print('init')
        
    def __enter__(self):
        print('enter')
        return self
    
    def __exit__(self,exc_type,exc_val,exc_tb):
        print(exc_type) # 错误类型
        print(exc_val)  # 值
        print(exc_tb)   # 错误提示,追踪
        print('exit')
pt = Point()
with pt as f: # 如果使用as 语法,其值为enter的返回值;
    print('~~~~~~~~~~~~~~~~')   # 程序开始时 先 init,enter
    raise Exception('your error')
    print(f)
#---------------------------------------------------------
init
enter
~~~~~~~~~~~~~~~~
<class 'Exception'>
your error
<traceback object at 0x000002DFBC0578C8>
exit
---------------------

exit 方法中 ;Return真值 会压制异常,不报错;

import time
class Point:
    def __init__(self):
        print('init')
        
    def __enter__(self):
        print('enter')
        return self
    
    def __exit__(self,exc_type,exc_val,exc_tb):
        print(exc_type) # 错误类型
        print(exc_val)  # 值
        print(exc_tb)   # 错误提示,追踪
        print('exit')
        return True
pt = Point()
with pt as f: # 如果使用as 语法,其值为enter的返回值;
    print('~~~~~~~~~~~~~~~~')   # 程序开始时 先 init,enter
    raise Exception('your error')
    print(f)
#--------------------------------------------------------------------------
init
enter
~~~~~~~~~~~~~~~~
<class 'Exception'>
your error
<traceback object at 0x000002DFBC0E8408>
exit
练习:为加法函数计时

方法1、使用装饰器显示该函数的执行时长
方法2、使用上下文管理方法来显示该函数的执行时长

import time
def add(x, y):
    time.sleep(2)
    return x + y

装饰器实现

import time
import datetime
from functools import wraps
def timeit(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs)
        delta = (datetime.datetime.now() - start).total_seconds()
        print('{} took {}s '.format(fn.__name__,delta))
        return ret
    return wrapper

@timeit
def add(x, y):
    time.sleep(2)
    return x + y

装饰器实现;

import time
import datetime
from functools import wraps
def timeit(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs)
        delta = (datetime.datetime.now() - start).total_seconds()
        print('{} took {}s '.format(fn.__name__,delta))
        return ret
    return wrapper

@timeit
def add(x, y):
    time.sleep(2)
    return x + y

class Timeit:
    def __init__(self, fn):
        self.fn = fn
    def __enter__(self):
        self.start = datetime.datetime.now()
        return self.fn
    def __exit__(self, exc_type, exc_val, exc_tb):
        delta = (datetime.datetime.now() - self.start).total_seconds()
        print("{} took {}s".format(self.fn.__name__, delta))
        
with Timeit(add) as fn:
    #print(fn(4, 6))
    print(add(4, 7))



# 类当做装饰器;
import time
import datetime
from functools import wraps

class Timeit:
    def __init__(self, fn):
        self.fn = fn
    def __enter__(self):
        self.start = datetime.datetime.now()
        return self.fn
    def __exit__(self, exc_type, exc_val, exc_tb):
        delta = (datetime.datetime.now() - self.start).total_seconds()
        print("{} took {}s".format(self.fn.__name__, delta))
    def __call__(self, *args, **kwargs):
        self.start = datetime.datetime.now()
        ret = self.fn(*args, **kwargs)
        delta = (datetime.datetime.now() - self.start).total_seconds()
        print("{} took {}s. call".format(self.fn.__name__, delta))
        return ret
        
@Timeit
def add(x,y):
    "This is add function."
    time.sleep(2)
    return x+y

add(4,5)
print(add.__doc__)
print(Timeit(add).__doc__)
#------------------------------------------------------
add took 2.000628s. call
None
None

上面的类即可以用在上下文管理,又可以用做装饰器

上下文应用场景

  1. 增强功能
    在代码执行的前后增加代码,以增强其功能。类似装饰器的功能。
  2. 资源管理
    打开了资源需要关闭,例如文件对象、网络连接、数据库连接等
  3. 权限验证
    在执行代码之前,做权限的验证,在 enter 中处理

2. contextlib.contextmanager

它是一个装饰器实现上下文管理,装饰一个函数,而不用像类一样实现 enterexit 方法。
对下面的函数有要求,必须有yield,也就是这个函数必须返回一个生成器,且只有yield一个值。
也就是这个装饰器接收一个生成器对象作为参数。

import contextlib

@contextlib.contextmanager
def foo():
    print('enter')
    try:
        yield # yield 5,yield的值只能有一个,作为__enter__方法的返回值
    finally:
        print('exit')
with foo() as f:
    raise Exception()
    print(f)

上例这么做有什么意义呢?

当yield发生处为生成器函数增加了上下文管理。这是为函数增加上下文机制的方式。
把yield之前的当做_enter方法执行
把yield之后的当做_exit
方法执行
把yield的值作为_enter_的返回值

相关文章

  • 31.3-魔术方法—上下文管理

    我们不能选择自己的原生家庭,但我们可以选择自己今后成为什么样的人。人生是一场马拉松,起步很重要,但可以抵达的终点更...

  • Python上下文管理器

    上下文管理器 概念:实现了上下文协议的对象即为上下文管理器。 上下文管理器的协议: __enter__进入的方法_...

  • Python33_上下文管理器

    上下文管理器 上下文管理器的方法: __enter__进入的方法,负责一些更改 __exit__退出方法,将进入时...

  • python 基础深入

    contextlib 上下文管理器 创建上下文管理实际就是创建一个类,添加enter和exit方法 Python...

  • 7.魔术方法(2)

    目录:1.上下文管理对象2.上下文管理的安全性3.with语句4.exit方法的参数 1.上下文管理对象 当一个对...

  • #魔术方法(会话管理)

    ### `__enter__`魔术方法: 使用`with`语句的时候,会调用这个魔术方法,这个方法的返回值可以作为...

  • Python魔术方法

    Python魔术方法 Python 魔术方法指南 — PyCoder’s Weelky CNPython的魔术方法...

  • 关于async with 表达式

    与 with 中的上下文管理器需要定义两个方法 enter() 和 exit() 一样,异步上下文管理器同样也需要...

  • 脱离Flask上下文,使用jinja2渲染html模板

    前言 首先,如果一个正常的flask带路由的接口,我们是不需要关心上下文对象的,Flask做了很多“魔术”的方法,...

  • python with语句

    常用用法: 一、术语: 上下文管理协议(Context Management Protocol):包含方法 ent...

网友评论

    本文标题:31.3-魔术方法—上下文管理

    本文链接:https://www.haomeiwen.com/subject/qqddoctx.html