Python-面向对象魔术方法之上下文管理

上下文管理

文件IO操作可以对文件对象使用上下文管理,使用with...as语法。 

with open('test',w+) as f:
    f.write('abc')

仿照上例写一个自己的类,实现上下文管理

class Point:
    pass
    def __enter__(self):
        pass
    def __exit__(self, exc_type, exc_val, exc_tb):
        pass

with Point() as p:
    pass

 

上下文管理对象

当一个对象同时实现了__enter__(),__exit__()方法,它就属于上下文管理的对象

方法 意义
__enter__ 进入此对象相关的上下文,如果存在该方法,with语句会把该方法的返回值作为绑定到as子句中指定的变量上
__exit__ 退出与此对象相关的上下文
import time

class Point:
    def __init__(self):
        print(1,'init~~~~~~~~~~~')
        time.sleep(1)
        print(2,'init end~~~~~~~')

    def __enter__(self):
        print(3,'enter~~~~~~~~~~~')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(4,'exit~~~~~~~~~~~')

with Point() as f:
    print(5,'in with ----------------')
    time.sleep(1)
    print(6,'with end------------')

print(7,'ending-----------------------')

#输出
1 init~~~~~~~~~~~
2 init end~~~~~~~
3 enter~~~~~~~~~~~
5 in with ----------------
6 with end------------
4 exit~~~~~~~~~~~
7 ending-----------------------

 实例化对象的时候,并不会调用enter,进入with语句块调用语句块调用__enter__方法,然后执行语句体,最后离开with语句块的时候,调用__exit__方法。

 

with可以开启一个上下文运行环境,在执行前做一些准备工作,执行后做一些收尾工作。

注意,with并不开启一个新的作用域。

 

上下文管理的安全性

看看异常对上下文的影响。

import time

class Point:
    def __init__(self):
        print('init~~~~~~~')
        time.sleep(1)
        print('init end~~~~')

    def __enter__(self):
        print('enter~~~~~~~')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('exit~~~~~~~~~')

with Point() as f:
    print('in with~~~~~~~~~~~~')
    1/0
    time.sleep(1)
    print('with over')

print('==============end==============')


#输出
init~~~~~~~
init end~~~~
enter~~~~~~~
in with~~~~~~~~~~~~
exit~~~~~~~~~
Traceback (most recent call last):
  File "D:/Python/test.py", line 322, in 
    1/0
ZeroDivisionError: division by zero

可以看出在enter和exit照样执行,上下文管理是安全的。

 

极端例子,调用sys.exit(),他会退出当前解释器。 

打开Python解释器,在里面敲入sys.exit(),窗口直接关闭了,也就是说碰到这句Python运行环境直接退出了。 

Python-面向对象魔术方法之上下文管理_第1张图片

 

 

 从上面执行结果来看,依然执行了__exit__函数,哪怕是退出了Python运行环境,说明上下文管理是很安全的。 

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')

f = open('test3.py')

with f as p:
    print(f)
    print(p)
    print(f is p)
    print(f == p)

p = f = None
print('------------------------')
p = Point()
with p as f:
    print('in with~~~~~~~~~~')
    print(p == f)
    print('with over')

print('======end=======')

with语法,会调用with后的对象的__enter__方法,如果有as,则将该方法的返回值赋值给as子句的变量。

上例等价于 f = p.__enter__()    

 

__enter__方法没有其他参数

__exit__方法有三个参数:

__exit__(self, exc_type, exc_val, exc_tb)

这三个参数都与异常有关。

如果该上下文退出时没有异常,这三个参数都为None

如果有异常,参数意义如下:

exec_type:异常类型

exec_val:异常值

exec_tb:traceback,异常的追踪信息

__exit__:方法返回一个等效True的值,则压制异常,否则继续抛出异常。

class Point:
    def __init__(self):
        print('init~~~~~~~~~~~~~~')

    def __enter__(self):
        print('enter~~~~~~~~~~~~~')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(1,exc_type)
        print(2,exc_val)
        print(3,exc_tb)
        print('exit~~~~~~~~~~~~~~')
        # return 123 #等效为True,则压制异常
        # return 'abc'
        return [] #等效为False则抛出异常


p = Point()
with p as f:
    print('in with ~~~~~~~~~~~')
    raise Exception('Error')
    print('with over')


print('========end=======')

#等效False输出

init~~~~~~~~~~~~~~
enter~~~~~~~~~~~~~
in with ~~~~~~~~~~~
1 <class 'Exception'>
2 Error
3 
exit~~~~~~~~~~~~~~
Traceback (most recent call last):
  File "D:/Python/test.py", line 383, in 
    raise Exception('Error')
Exception: Error

#等效True输出
init~~~~~~~~~~~~~~
enter~~~~~~~~~~~~~
in with ~~~~~~~~~~~
1 <class 'Exception'>
2 Error
3 
exit~~~~~~~~~~~~~~
========end=======

 为加法函数计时

方法1、使用装饰器显示该函数的执行时长

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

装饰器实现:

import time
import datetime
from functools import wraps
from functools import update_wrapper

def timeit(fn):
    @wraps(fn)
    def wrapper(*args,**kwargs):
        '''This is  wrapper functioin'''
        start = datetime.datetime.now()
        ret = fn(*args,**kwargs)
        delta = (datetime.datetime.now() - start).total_seconds()
        print('{} took {}s'.format(fn.__name__,delta))
        # update_wrapper(wrapper,fn)
        return ret
    return wrapper

@timeit # add=timeit(add)
def add(x,y):
    '''This is add function'''
    time.sleep(2)
    return x + y

print(add(4,5))

 

上下文实现

import time
import datetime
from functools import update_wrapper

def timeit(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))
        update_wrapper(wrapper,fn)
        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(add(4,5))

 

另一种实现,使用可调用对象实现

import time
import datetime
from functools import update_wrapper

def timeit(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))
        update_wrapper(wrapper,fn)
        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

    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, x, y):
        return self.fn(x,y)
    

with Timeit(add) as fn:
    print(add(4,5))

 

将类当成装饰器用

import time
import datetime
from functools import wraps,update_wrapper

class Timeit:
    def __init__(self,fn):
        self.fn = fn
        # self.__doc__ = fn.__doc__
        # update_wrapper(self,fn)
        wraps(fn)(self)

    def __call__(self, *args, **kwargs):
        self.start = datetime.datetime.now()
        ret = self.fn(*args,**kwargs)
        self.delta = (datetime.datetime.now() - self.start).total_seconds()
        print("{} took {}s call".format(self.fn.__name__,self.delta))
        return ret

@Timeit #  add = Timeit(add)
def add(x,y):
    '''This is add function'''
    time.sleep(2)
    return x + y

print(add(4,5))
print(add.__doc__)

 

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

 

上下文管理应用场景

1.增强功能

  在代码执行的前后增加代码,以增强其功能,类似装饰器的功能

2.资源管理

  打开了资源需要关闭,例如文件对象、网络连接、数据库连接等

3.权限验证

  在执行代码之前,做权限的验证,在__enter__中处理

 

 contextlib.contextmanager

contextlib.contextmanager:它是一个装饰器实现上下文管理,装饰一个函数,而不像类一样实现__enter__和__exit__方法

对下面的函数有要求:必须有yield,就是函数必须返回一个生成器,且只有yield一个值。

也就是这个装饰器就收一个生成器对象作为参数。 

 

import contextlib

@contextlib.contextmanager
def foo():
    print('enter')
    yield 123
    print('exit')

with foo() as f:
  #raise Exception
print(f) enter 123 exit

f接收yield语句的返回值。

上面的程序看似不错,但是,增加异常,发现不能保证exit执行,需增加try finally

import contextlib

@contextlib.contextmanager
def foo():
    print('enter')
    try:
        yield 123
    finally:
        print('exit')

with foo() as f:
    raise Exception('error')
    print(f)

 

编写一个支持上下文的函数timeit,完成对add函数的计时

from contextlib import contextmanager
import time
import datetime

@contextmanager
def timeit():
    print('enter')
    start = datetime.datetime.now()
    try:
        yield
    finally:
        print('exit')
        delta = (datetime.datetime.now() - start).total_seconds()
        print(delta)

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

with timeit():
    # 1/0 #此处异常,后续的语句将不再执行
    add(4,5)

 

你可能感兴趣的:(Python-面向对象魔术方法之上下文管理)