Python世界里的魔术方法(二)

上下文管理

__enter__

__exit__

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

with open('test') as f:
    pass

如果希望类也支持上下文管理,则需要定义两个函数。

class A:
    def __init__(self):
        print('init')
        
    def __enter__(self):
        print('enter')

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


with A() as a:
    print(a) # a 此时为None
    
# 输出:
# init
# enter
# None
# exit

进入with语句块时,会调用__enter__,退出with语句块时,调用__exit__。

因此,退出exit函数时,可以做清理工作,比如关闭文件描述符。即使异常退出,也会执行上下文。因此,涉及到资源申请的操作,最好使用上下文管理语句。

注意:先实例化,才能调用enter。因为enter是实例方法。 只有with语法时,才会和enter,exit等函数相关,如此才会开启上下文。

上下文管理对象

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

方法意义
__enter__进入与此对象相关的上下文。如果存在此方法,with语法会把该方法返回值作为绑定到as子句中指定的变量上
__exit__退出与此对象相关的上下文,包含三个参数:异常类型,异常的值,异常的traceback。
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:
    raise Exception('error')  # 即使抛出异常,也会执行exit。因此,上下文管理是安全的。即使sys.exit也是安全的
    print('do sth.')

注意:如果__exit__返回True,会压制异常。

函数的上下文和类的上下文

  • 函数的上下文,是同一个对象:

    f = open('1.py')
    with f as a:
        print(f == a) # 同一个对象
        print(id(f),id(a))
    
  • 类的上下文,则不同:

    with A() as a:
        print(a) # None 返回__enter__的return值
    
    with A(): # 
        print(A()) # 返回实例
    # a 和A() 是不同对象
    

上下文应用场景

  • 增强功能

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

  • 资源管理

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

  • 权限验证

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

应用:使用上下文,实现函数计时器

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 f:
    print(add(4,6))

contextlib.contextmanager

它是一个装饰器,装饰一个函数后可实现上下文管理。

而无需像类一样实现__enter__和__exit__ 方法。

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

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

import contextlib

@contextlib.contextmanager
def foo():
    print('enter') # 相当于__enter__()
    yield 5 # yield的值只能有一个,相当于__enter__方法的返回值
    print('exit') # 相当于__exit__()


with foo() as f:
    print(f)

输出:

enter
5
exit

f接收了yield语句的返回值。

但是很明显无法保证exit的执行,如果产生了异常就直接退出。因此增加try finally。

import contextlib

@contextlib.contextmanager
def foo():
    print('enter') # 相当于__enter__()
    try:
        yield 5 # yield的值只能有一个,相当于__enter__方法的返回值
    finally:
        print('exit') # 相当于__exit__()


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

如此保证了enter和exit的正常执行。

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

  • 把yield之前的当做方法执行
  • 把yield之后的当做__exit____方法执行
  • 把yield的值作为__enter__的返回值

因此,如果业务逻辑简单可以使用函数加contextlib的装饰器方式,如果业务复杂,则使用类的方式加__enter__和__exit__方法方便。

反射相关的魔术方法

__getattr__

__setattr__

__delattr__

__getatrribute__

具体可参考《浅谈Python中的反射》

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值