目录

上下文管理... 1

习题... 4

类装饰器... 8

contextlib.contextmanager. 9

functools.total_ordering. 10

 

 

上下文管理

文件io操作,可对文件对象使用上下文管理,用with ... as...语法;

with open('test.txt') as f:

         pass

 

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

 

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

__enter__()要求要有返回值,该返回值就是上下文中使用的对象,进入与此对象相关的上下文,with语法会把__enter__()方法的返回值绑定到as子句的变量上;

__exit__(),退出与此对象相关的上下文,有返回值,返回值若为等效True则压制异常;返回值若为等效False则向外继续抛异常(pycharmRun中会有红色异常信息),且with同级之后的语句不会执行;

进入with语句块调用__enter__(),执行语句体,离开with语句块时调用__exit__()

 

__enter__(),没有其它参数;

 

__exit__()参数:

def __exit__(self, exc_type, exc_val, exc_tb):

三个参数都与异常有关;

exc_type,异常类型;

exc_val,异常的值;

exc_tbtraceback异常的追踪信息;

 

上下文管理的安全性:

上下文管理非常安全;

即使有异常,__enter__()__exit__()照样执行,上下文管理是安全的;

 

上下文应用场景:

1、增强功能,在代码执行的前后增加代码,类似装饰器;

2、资源管理,打开了资源需要关闭,如文件对象、网络连接、数据库连接;

3、权限验证,在执行代码之前,作权限的验证,在__enter__中处理;java中叫拦截器;

 

例:

class Point:   #类中无定义__enter____exit__方法,用with Point() as f: pass打开时会报错AttributeError

    pass

 

with Point() as f:   #with Point as f: pass,错误写法,魔术方法的第一个参数始终为self为实例的

    pass

输出:

Traceback (most recent call last):

  File "/home/python/magedu/projects/cmdb/example_class_magicmethod8.py", line 5, in <module>

    with Point() as f:

AttributeError: __enter__

 

例:

class Point:

    def __init__(self):

        print('init')

 

    def __enter__(self):

        print(self.__class__)

        return self   #该方法的返回值会绑到as子句的变量f

 

    def __exit__(self, exc_type, exc_val, exc_tb):

        print(self.__class__.__name__)

 

with Point() as f:   #实例化对象的时候,并不会调用__enter__,进入with语句块调用__enter__方法,然后执行语句体,最后离开with语句时调用__exit__方法

    print('do nothing')

 

p = Point()

with p as f:   #pf有关系,__enter__方法的返回值会绑定到as子句的变量f

    print(f == p)

    print(f is p)

    print(f)

    print(p)

输出:

init

<class '__main__.Point'>

do nothing

Point

init

<class '__main__.Point'>

True

True

<__main__.Point object at 0x7f8dcef0db38>

<__main__.Point object at 0x7f8dcef0db38>

Point

 

例:

异常对上下文管理的影响;

import sys

with p as f:   #sys.exit(),极端,会退出当前解释器,sys.exit()后的语句不会执行,但__exit__()中的语句会执行

    # raise Exception('error custom')

    sys.exit()

    print(f)

    print(p)

输出:

init

<class '__main__.Point'>

<class 'SystemExit'>

 

<traceback object at 0x7fd95a62e448>

 

例:

class Point:

    def __init__(self):

        print('init')

 

    def __enter__(self):

        print(self.__class__)

        return self

 

    def __exit__(self, exc_type, exc_val, exc_tb):

        # print(self.__class__.__name__)

        print(exc_type)

        print(exc_val)

        print(exc_tb)

        return 1   #等效True1123[1]

        # return 0   #等效False0、四大皆空,向外抛出异常,且与with同级之后的语句不会执行

 

p = Point()

with p as f:

    raise Exception('error custom')   #若将此行注释,则无异常,__exit__()中的三个参数,有异常时会显示异常信息,无异常时则为None

    # sys.exit()

    print(f)

    print(p)

print('end')

输出:

init

<class '__main__.Point'>

<class 'Exception'>

error custom

<traceback object at 0x7fc5deada4c8>

end

 

 

 

习题

为加法函数计时:

1、使用装饰器;

2、使用上下文管理器;

 

1

import datetime

from functools import wraps

import time

 

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

 

print(add(4,5))

输出:

add took 2.002281s

9

 

2

import datetime

import time

 

class TimeIt:

    def __enter__(self):

        print('__enter__')

        self.start = datetime.datetime.now()

        return self

 

    def __exit__(self, exc_type, exc_val, exc_tb):

        print('__exit__')

        delta = (datetime.datetime.now() - self.start).total_seconds()

        print(delta)

 

def add(x,y):

    time.sleep(2)

    return x + y

 

with TimeIt() as f:

    print(add(4,5)).

输出:

__enter__

9

__exit__

2.002382

 

改进1

调用时写成这种形式:

with TimeIt(add) as foo:

         print(foo(4,5))

 

改进1,方1

import datetime

import time

 

class TimeIt:

    def __init__(self,fn):

        self._fn = fn

 

    def __enter__(self):

        print('__enter__')

        self.start = datetime.datetime.now()

        # return self

        return self._fn   #返回传入的函数add

 

    def __exit__(self, exc_type, exc_val, exc_tb):

        print('__exit__')

        delta = (datetime.datetime.now() - self.start).total_seconds()

        # print(delta)

        print('{} took {}s'.format(self._fn.__name__,delta))

 

    def __repr__(self):

        return str(self._fn)

 

    __str__ = __repr__

 

def add(x,y):

    time.sleep(2)

    return x + y

 

# with TimeIt() as f:

#     print(add(4,5))

 

with TimeIt(add) as foo:

    print(foo(4,5))

输出:

__enter__

9

__exit__

add took 2.002387s

 

改进1,方2

import datetime

import time

 

class TimeIt:

    def __init__(self,fn):

        self._fn = fn

 

    def __enter__(self):

        print('__enter__')

        self.start = datetime.datetime.now()

        return self

        # return self._fn

 

    # def __call__(self,x,y):   #X,错误,如果这样写会跳过原本被装饰的函数add

    #     return x + y

 

    def __call__(self, *args, **kwargs):   #可调用对象当函数用,类装饰器

        print('__call__')

        return self._fn(*args,**kwargs)

 

    def __exit__(self, exc_type, exc_val, exc_tb):

        print('__exit__')

        delta = (datetime.datetime.now() - self.start).total_seconds()

        # print(delta)

        print('{} took {}s'.format(self._fn.__name__,delta))

 

    def __repr__(self):

        return str(self._fn)

 

    __str__ = __repr__

 

def add(x,y):

    time.sleep(2)

    return x + y

 

with TimeIt(add) as foo:

    print(foo(3,3))

输出:

__enter__

6

__exit__

add took 2.002454s

 

 

 

类装饰器

 

类装饰器要有__init__()__call__()

 

如果业务逻辑简单,可使用函数 + 装饰器方式;

如果业务复杂,用类的方式 + __enter__()__exit__()

 

例:

装饰add函数;

@TimeIt

复制被装饰函数的属性,functools.wraps

 

import datetime

import time

from functools import wraps

 

 

class TimeIt:

    def __init__(self,fn):

        self._fn = fn

        # self.__name__ = self._fn.__name__   #如果这样写,要写一大堆属性,要用functools.wraps

        # self.__doc__ = self._fn.__doc__

        wraps(fn)(self)

 

    def __call__(self, *args, **kwargs):

        print('__call__')

        start = datetime.datetime.now()

        ret = self._fn(*args,**kwargs)

        delta = (datetime.datetime.now() - start).total_seconds()

        print('{} took {}'.format(self._fn.__name__,delta))

        return ret

 

@TimeIt

def add(x,y):

    """this is a add function"""

    time.sleep(2)

    return x + y

 

print(add(2,3))

print(add.__doc__)

print(add.__name__)

print(add.__dict__)

print(type(add))   #装饰完后,add就是TimeIt的实例了,当前作用域的all已经不再是之前函数中定义的而是实例,只被TimeIt中的fn引用

输出:

__call__

add took 2.002285

5

this is a add function

add

{'_fn': <function add at 0x7fa4adb7fe18>, '__module__': '__main__', '__name__': 'add', '__qualname__': 'add', '__doc__': 'this is a add function', '__annotations__': {}, '__wrapped__': <function add at 0x7fa4adb7fe18>}

<class '__main__.TimeIt'>

 

 

 

contextlib.contextmanager

 

这是一个装饰器实现的上下文管理,装饰一个函数,而不用像类一样实现__enter__()__exit__()比类要轻巧

 

必须有yield,这个函数必须返回一个生成器,且yield值只能有一个;

yield的发生处为生成器函数增加了上下文管理;

 

例:

import contextlib

 

@contextlib.contextmanager

def foo():

    print('enter')

    try:

        yield 5   #yield值只能有一个,作为__enter__()方法的返回值(把yield值塞到__enter__()方法中作为返回值),在yield的发生处为生成器函数增加了上下文管理

    finally:

        print('exit')

 

with foo() as f:

    # raise Exception('custom error')

    print(f)

输出:

enter

5

exit

 

例:

@contextlib.contextmanager

def add(x,y):

    time.sleep(2)

    yield x + y

 

with add(4,5) as f:

    print('decorator',f)

输出:

decorator 9

 

 

 

functools.total_ordering

 

__lt____le____eq____gt____ge__,是比较大小必须实现的方法,但全部写完太麻烦,使用此装饰器可大大简化代码;

查看源码,该函数实现复杂,效率不高;

此装饰器要求,类中必须实现__eq__(),另在其它比较方法中选一个即可;

 

如,定义了__eq__()__lt__(),在用其它方法时,该装饰器才推演(大量计算),在用到==<时则直接使用,不进行推演;

 

在比较时,若不用此装饰器,则要把所有的比较方法写全,如写不全则报错TypeError: '<' not supported between instances of 'A' and 'A',例如只写了__ge__()方法,在使用>=<=时没问题,但当使用<>时报错;

 

functools.total_ordering应用场景:

偶尔比较时;

若经常比较,要把比较方法全部写到类里,这样效率高;

 

例:

from functools import total_ordering

 

@total_ordering

class A:

    def __init__(self,x):

        self.x = x

 

    # def __ge__(self, other):

    #     return self.x >= other.x

 

    def __eq__(self, other):

        return self.x == other.x

 

    def __lt__(self, other):

        return self.x <= other.x

 

a = A(5)

b = A(6)

print(a >= b)

print(a <= b)

print(a < b)

print(a > b)

输出:

False

True

True

False