1-上下文管理器之__enter__和__exit__

about

有个学生在第四轮面试中被CTO问到:如何自定义实现with open的功能。然后就一脸懵逼的回来找我了……
看到with open大家都应该知道,这是在问关于上下文管理协议,这个所谓的上下文管理协议一般是指我们在文件操作中常用的那个with ... as ..

Python允许with语句使用上下文管理器,而上下文管理器则是支持__enter____exit__方法的对象。__enter__方法没有参数,当程序执行到with语句时被调用,返回值绑定在文件对象f上,而__exit__方法则有三个参数,包括异常类型,异常对象,异常回溯。

with as是如何工作的

我们通过示例来了解with ... as ...语句是如何工作的,先来个简单的:

class MyOpen1(object):

    def __enter__(self):
        print('当with语句开始执行时,我被执行啦…………')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('当with语句执行完毕,我被执行啦…………')
with MyOpen1() as f1:
    print('假装再做文件操作…………')

'''打印结果
当with语句开始执行时,我被执行啦…………
假装再做文件操作…………
当with语句执行完毕,我被执行啦…………
'''

上例的打印结果印证了我们之前所说。并且,我们知道当MyOpen1加括号实例化的时候,还触发了实例化方法__init__的执行:

class MyOpen2(object):

    def __init__(self):
        print('我是__init__方法,当 {} 加括号时,我被执行啦…………'.format(self.__class__.__name__))

    def __enter__(self):
        print('当with语句开始执行时,我被执行啦…………')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('当with语句执行完毕,我被执行啦…………')

with MyOpen2() as f2:
    print('假装再做文件操作…………')

'''打印结果
我是__init__方法,当 MyOpen2 加括号时,我被执行啦…………
当with语句开始执行时,我被执行啦…………
假装再做文件操作…………
当with语句执行完毕,我被执行啦…………
'''

自定制open方法

由上面的两个示例我们隐约可以明白with ... as ..语句内部是怎么玩的了。我们可以试着更深一步的完善代码:

class MyOpen3(object):

    def __init__(self, file_obj, mode='r', encoding='utf-8'):
        self.file_obj = file_obj
        self.mode = mode
        self.encoding = encoding

    def __enter__(self):
        self.f = open(file=self.file_obj, mode=self.mode, encoding=self.encoding)
        return self.f

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.f.close()

with MyOpen3('a.txt', mode='w', encoding='utf-8') as f3:
    f3.write('custom with ... as ...')

结果已经不用多说,完美!

更多的示例

更多的示例和其他的扩展:

class MyOpen(object):
    """ 手动实现with open """

    def __init__(self, file_obj, mode='r', encoding=None):
        """
        当实例化的时候,需要传递三个参数
        :param file_obj: 文件名称(路径)
        :param mode: 操作模式
        :param encoding: 编码
        """
        # 调用内置函数open,获取文件对象f
        self.f = open(file_obj, mode=mode, encoding=encoding)

    def __enter__(self):
        """
        当with语句被调用时,返回文件对象f
        :return: f
        """
        print('with语句触发__enter__执行')
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        """
        退出关联到此对象的运行时上下文。 各个参数描述了导致上下文退出的异常。 如果上下文是无异常地退出的,三个参数都将为 None
        :param exc_type:
        :param exc_value:
        :param traceback:
        :return:
        """
        self.f.close()
        print('文件对象 f 已被关闭')

    def read(self):
        """
        自定义读方法
        """
        print('自定义的read方法被执行')
        return self.f.read()

    def write(self, content):
        self.f.write(content)

    def __getattr__(self, item):
        """
        当自定义的文件对象调用MyOpen类中没实现个方法时,就通过getattr,从内置函数的open对象中返回
        :param item: 比如seek方法
        :return: 将seek方法返回
        """
        return getattr(self.f, item)

f = MyOpen('a.txt', 'a', "utf8")
f.write('我爱北京天安门')
f.close()
f = MyOpen('a.txt', 'r', "utf8")
print(f.read())
f.close()

with MyOpen('a.txt', 'r+', "utf8") as f1:
    # print(f1.__doc__)   # 通过打印结果可以看到,with语句使用__enter__方法将MyOpen实例化的对象赋值给f1
    f1.seek(3)  # 注意,由于utf8编码对普通中文用3个字节表示,所以seek的时候,需要注意
    f1.write('我是你爸爸')
    print(f1.read())

再来一个搭配pickle的:
ps:原谅洒家无耻的拷贝!

import  pickle
class MyPickledump:
    def __init__(self,path):
        self.path = path

    def __enter__(self):
        self.f = open(self.path, mode='ab')
        return self

    def dump(self,content):
        pickle.dump(content,self.f)

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.f.close()

class Mypickleload:
    def __init__(self,path):
        self.path = path

    def __enter__(self):
        self.f = open(self.path, mode='rb')
        return self


    def __exit__(self, exc_type, exc_val, exc_tb):
        self.f.close()

    def load(self):
         return pickle.load(self.f)


    def loaditer(self):
        while True:
            try:
                yield  self.load()
            except EOFError:
                break

# with MyPickledump('file') as f:
#      f.dump({1,2,3,4})

with Mypickleload('file') as f:
    for item in f.loaditer():
        print(item)

with和pickle

更多的:

import  pickle
class MyPickledump:
    def __init__(self,path):
        self.path = path

    def __enter__(self):
        self.f = open(self.path, mode='ab')
        return self

    def dump(self,content):
        pickle.dump(content,self.f)

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.f.close()

class Mypickleload:
    def __init__(self,path):
        self.path = path

    def __enter__(self):
        self.f = open(self.path, mode='rb')
        return self


    def __exit__(self, exc_type, exc_val, exc_tb):
        self.f.close()

    def __iter__(self):
        while True:
            try:
                yield  pickle.load(self.f)
            except EOFError:
                break



# with MyPickledump('file') as f:
#      f.dump({1,2,3,4})

with Mypickleload('file') as f:
    for item in f:
        print(item)

with和pickle和iter

关于__exit__的返回值:return True

class Resource:
    def __enter__(self):
        print('in __enter__')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('in __exit__', exc_type)
        return True

    def operate(self):
        print('in func')


with Resource() as res:
    res.operate()
    # 如果with中发生了报错,如下面的raise,那么报错信息会传递给__exit__
    # 如果__exit__中没有return True,那么整个程序仍然会报错
    # 如果__exit__中有return True,那么整个程序就不会报错
    raise ZeroDivisionError  # 随便模拟个被除数不能为0的错误

"""
如果__exit__中没有return True,控制台输出结果
    Traceback (most recent call last):
      File "D:\tests.py", line 19, in <module>
        raise ZeroDivisionError
    ZeroDivisionError
    in __enter__
    in func
    in __exit__ <class 'ZeroDivisionError'>

如果__exit__中有return True,控制台输出结果
    in __enter__
    in func
    in __exit__ <class 'ZeroDivisionError'>
"""

see also:

Python官网关于with的描述 | PEP 343 with语句 | with 语句上下文管理器 | Context Manager Types | Python概念-上下文管理协议中的__enter__和__exit__ | python之路——面向对象进阶
欢迎斧正,that's all

posted @ 2019-04-10 00:08  听雨危楼  阅读(398)  评论(0编辑  收藏  举报