魔术方法2--上下文管理

魔术方法2

上下文管理对象

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

import time

class Point:
    def __init__(self):
        print('init ~~~~~~~~~~~~~~~')
        time.sleep(1)
        print('init over')
        
    def __enter__(self):
        print('enter ~~~~~~~~~~~~~~')
        
    def __exit__(self, exc_type, exc_val, exc_tb):
        print('exit ========================')
        
with Point() as p:
    print('in with------------')
    
    time.sleep(2)
    print('with over')
    
print('=============end==============')

init ~~~~~~~~~~~~~~~
init over
enter ~~~~~~~~~~~~~~
in with------------
with over
exit ========================
=============end==============

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

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

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

上下文管理的安全性

看看异常对上下文的影响

import time

class Point:
    def __init__(self):
        print('init ~~~~~~~~')
        time.sleep(1)
        print('init over')
        
    def __enter__(self):
        print('enter ~~~~~~~~')
        
    def __exit__(self, exc_type, exc_val, exc_tb):
        print('exit =============')
        
with Point() as p:
    print('in with ---------')
    raise Exception('error')
    time.sleep(2)
    print('with over')
    
print('=============end===============')
init ~~~~~~~~
init over
enter ~~~~~~~~
in with ---------
exit =============



---------------------------------------------------------------------------

Exception                                 Traceback (most recent call last)

<ipython-input-6-a9563eb44b9a> in <module>
     15 with Point() as p:
     16     print('in with ---------')
---> 17     raise Exception('error')
     18     time.sleep(2)
     19     print('with over')


Exception: error

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


极端的例子

调用sys.exit(),它会退出当前解释器。

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

import time

class Point:
    def __init__(self):
        print('init ~~~~~~~~')
        time.sleep(1)
        print('init over')
        
    def __enter__(self):
        print('enter ~~~~~~~~')
        
    def __exit__(self, exc_type, exc_val, exc_tb):
        print('exit =============')
        
with Point() as p:
    print('in with ---------')
    import sys
    sys.exit(1)
    time.sleep(2)
    print('with over')
    
print('=============end===============')
init ~~~~~~~~
init over
enter ~~~~~~~~
in with ---------
exit =============



An exception has occurred, use %tb to see the full traceback.


SystemExit: 1



d:\miniconda3\lib\site-packages\IPython\core\interactiveshell.py:3334: UserWarning: To exit: use 'exit', 'quit', or Ctrl-D.
  warn("To exit: use 'exit', 'quit', or Ctrl-D.", stacklevel=1)

从执行结果来看,依然执行了__exit__函数,哪怕是退出Python运行环境。

说明上下文管理很安全。


with语句

# t3.py文件中写入下面代码
class Point:
    def __init__(self):
        print('init ~~~~~~~~')
        
    def __enter__(self):
        print('enter ~~~~~~~~')
        
    def __exit__(self, exc_type, exc_val, exc_tb):
        print('exit =============')
        
f = open('t3.py')
with f as p:
    print(f)
    print(p)
    print(f is p)
    print(f == p)  
    
# p = f = None  

p = Point()
with p as f:
    print('in with ------------')
    print(p == f)  # enter方法将自己的返回值赋给了f
    print('with over')
    
print('=========end============')
<_io.TextIOWrapper name='t3.py' mode='r' encoding='cp936'>
<_io.TextIOWrapper name='t3.py' mode='r' encoding='cp936'>
True
True
init ~~~~~~~~
enter ~~~~~~~~
in with ------------
False
with over
exit =============
=========end============

问题在于__enter__上,它将自己的返回值赋给了f。修改上例

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

p = Point()
with p as f:
    print('in with ------------')
    print(p == f)  # enter方法将自己的返回值赋给了f
    print('with over')
    
print('=========end============')
init ~~~~~~~~
enter ~~~~~~~~
in with ------------
True
with over
exit =============
=========end============

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

上例,可以等价为f = p.__enter__()

方法的参数

__enter__方法 没有其他参数

__exit__方法有三个参数:

  • __exit__(self, exc_type, exc_value, exc_tb)

这三个参数都与异常有关,这三个参数都为None

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

  • exc_type,异常类型
  • exc_value,异常的值
  • exc_tb,异常的追踪信息
  • __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 1
        
        
p = Point()
with p as f:
    print('in with ------------')
    raise Exception('Error')
    print('with over')
    
print('=========end============')
init ~~~~~~~~
enter ~~~~~~~~
in with ------------
1 <class 'Exception'>
2 Error
3 <traceback object at 0x0000023295B70588>
exit ============
=========end============

练习

为加法函数计时

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

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

装饰器实现
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(delta)
        return ret
    return wrapper

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

print(add(4, 5))

2.003499
9
上下文实现
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(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(delta)
        
with Timeit(add) as f:
    print(add(4, 6))
2.000753
10
2.000753
将类当作装饰器用
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(delta)
        
    def __call__(self, *args):
        self.start = datetime.datetime.now()
        ret = self.fn(*args)
        self.delta = (datetime.datetime.now() - self.start).total_seconds()
        print(self.delta)
        return ret
 

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

add(4, 5)
print(add.__doc__)
# print(add.__name__)  # 异常,没有此属性
2.00617
None



---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-24-afc72ebf40ba> in <module>
     32 add(4, 5)
     33 print(add.__doc__)
---> 34 print(add.__name__)  # 异常,没有此属性


AttributeError: 'Timeit' object has no attribute '__name__'

思考

如何解决文档字符串问题?

方法一:直接修改__doc__

class Timeit:
    def __init__(self, fn=None):
        self.fn = fn
        # 把函数对象的文档字符串赋给类
        self.__doc__ = fn.__doc__

方法二:使用functools.wraps函数

import time
import datetime
from functools import wraps


class Timeit:
    """This is A Class"""
    
    def __init__(self, fn):
        self.fn = fn
        # 把函数对象的文档字符串赋给类
        # self.__doc__ = fn.__doc__
        # update+wrapper(self, fn)
        wraps(fn)(self)
        
    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(delta)
        
    def __call__(self, *args):
        self.start = datetime.datetime.now()
        ret = self.fn(*args)
        delta = (datetime.datetime.now() - self.start).total_seconds()
        print(delta)
        return ret
 

@Timeit
def add(x, y):
    """this is add function"""
    time.sleep(2)
    return x + y

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

print(Timeit(add).__doc__)
2.000463
15
this is add function
this is add function

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


上下文应用场景

1、增强功能

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

2、资源管理

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

3、权限验证

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

contextlib.contextmanager

contextlib.contextmanager

它是一个装饰器实现上下文管理,装饰一个函数,而不用像类一样实现__enter____exit__方法。

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

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

当yield发生处生成器函数增加了上下文管理。这就是为函数增加上下文机制的方式。

  • 把yield之前的当作__enter__方法执行
  • 把yield之后的当作__exit__方法执行
  • 把yield的值作为__enter__的返回值
import contextlib
import datetime
import time

@contextlib.contextmanager
def add(x, y):  # 为生成器函数增加了上下文管理
    start = datetime.datetime.now()
    try:
        time.sleep(2)
        yield x + y  # yield 5,yield的值只能有一个,作为`__enter__`方法的返回值
    finally:
        delta = (datetime.datetime.now() - start).total_seconds()
        print(delta)
        
with add(4, 5) as f:
#     raise Exception()
    print(f)
2.000723



---------------------------------------------------------------------------

Exception                                 Traceback (most recent call last)

<ipython-input-28-fdd9d911ac66> in <module>
     14 
     15 with add(4, 5) as f:
---> 16     raise Exception()
     17     print(f)


Exception: 

总结:

如果业务逻辑简单可以使用函数加contextlib.contextmanager装饰器方式,如果业务复杂,用类的方式加__enter____exit__方法方便。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值