• python的魔术方法(容器和上下文管理)


    一、容器相关方法

    • __len__ : 内建函数len(),返回对象的长度(>=0的整数),其实即使把对象当做容器类型来看,就如同list或者dict。bool()函数调用的时候,如果没有__bool__()方法,则会看__len__()方法是否存在,存在返回非0为真
    • __iter__ : 迭代容器时,调用返回一个新的迭代器对象
    • __contains__ : in成员运算符,没有实现就调用__iter__方法遍历
    • __getitem__ : 实现self[key]访问,序列对象,key接受整数位索引,或者切片,对应set和dict,key为hashable,key不存在引发KeyError异常
    • __setitem__ : 和__getiem__的访问类似,是设置值的方法
    • __missing__ : 字典使用__getitem__()调用时,key不存在执行该方法
    将购物车类改造成方便操作的容器类
    
    class Cart:
        def __init__(self):
            self.items = []
            print(1111111111)
        
        def __len__(self):
            print(777777777777777)
            return len(self.items)
        
        def additem(self,item):
            print(66666666666)
            self.items.append(item)
        
        def __add__(self,other):
            self.items.append(other)
            print(5555555555)
            return self
        
        def __getitem__(self,index):
            print(3333333333333)
            return self.items[index]
            
        
        def __setitem__(self,key,value):
            print(key,value)
            print(44444444444444444)
            self.items[key] = value
            
        def __iter__(self):
            print(22222222222222)
            return iter(self.items)
            
        
        def __repr__(self):
            print(88888888888888)
            return str(self.items)
    
    cart = Cart()
    cart.additem(1)
    cart.additem('a')
    cart.additem(2)
    #长度
    print(len(cart))
    
    #迭代操作
    for x in cart:
        print(x)
    
    #in操作
    print(2 in cart)
    
    print('=================索引操作=================')
    print(cart[1])
    cart[2] = 'b'
    print(cart[2])


    二、可调用对象

    • __call__方法: 类中第一个该方法,实例就可以像函数一样调用
    • 可调用对象:定义一个类,并实例化得到其实例,将实例像函数一条调用
    举例:
    
        def foo():
            print(foo.__module__,foo.__name__)
    
    foo()
    #等价于
    foo.__call__();函数即对象,对象foo加上(),就是调用对象的__call__()方法


    1、代码举例

    class Point:
        def __init__(self,x,y):
            print(11111111111)
            self.x = x
            self.y = y
        
        def __call__(self,*args,**kwargs):
            print(222222222222)        
            return "Point({},{})".format(self.x,self.y)
    
    p = Point(4,5)
    print(p)
    print(p())
    
    执行输出:
            11111111111
            <__main__.Point object at 0x0000000004973978>
            222222222222
            Point(4,5)
    
    定义一个斐波那契数列的类,方便调用,计算第n项
    
    class Fib:
        def __init__(self):    # 实例初始化
            self.items = [0,1,1]
        
        def __call__(self,index):  #索引访问
            if index < 0:
                raise IndexErrot('Wrong Index')
            
            if index < len(self.items):
                return self.items[index]
            
            for i in range(3, index+1):
                self.items.append(self.items[i-1] + self.items[i-2])
            
            return self.items[index]
    
    print(Fib()(100))
    
        
    上例中,增加迭代的方法,返回容器长度,支持索引的方法
    class Fib:
        def __init__(self):  # 实例初始化
            self.items = [0,1,1]
        
        def __call__(self, index):   #索引访问
            return self[index]
        
        def __iter__(self):   # 迭代访问函数
            return iter(self.items)
        
        def __len__(self):   # 长度
            return len(self.items)
        
        def __getitem__(self, index):   # 获取
            if index < 0:
                raise IndexErrot('Wrong Index')
            if index < len(self.items):
                return self.items[index]
            
            for i in range(len(self), index+1):
                self.items.append(self.items[i-1] + self.items[i-2])
            return self.items[index]
        
        def __str__(self):
            return str(self.items)
        
        __repr__ = __str__
    
    fib = Fib()
    print(fib(5), len(fib))
    print(fib(10), len(fib))
    
    for x in fib:
        print(x)
        
    print(fib[5], fib[6]) #索引访问


    三、上下文管理

    • 文件IO操作可以对文件对象使用上下文管理,使用with...as语法 :with open('test') as f:
    • 当一个对象同时实现了__enter__()和__exit__()方法,它就属于上下文管理的对象
    • __enter__: 进入与此对象相关的上下文,如果存在该方法,with语法会把该方法的返回值作为绑定到as字句中指定的变量上
    • __exit__ : 退出与此对象相关的上下文
    举例1:
        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:
            print('do sth.')
    
    实例化对象的时候,并不会调用enter,进入with语句块调用__enter方法,然后执行语句体,
    最后离开with语句块的时候,调用__exit__方法
    with可以开启一个上下文运行环境,在执行前做一些准备工作,执行后做一些收尾工作


    1、上下文管理的安全性

    举例1
    
    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')
        print('do sth.')
       
       可以看出有异常enter和exit照样执行了,上下文管理是安全的
    
    
    举例2
    import sys
    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:
        sys.exit()
        print('do sth.')
    
    print('outer')
    
    调用sys.exit(),它会退出当前解释器,打开python解释器,在里面敲入sys.exit(),窗口直接关闭也就是说碰到这一句,python运行环境直接退出了
    从执行结果来看,依然执行了__exit__函数,哪怕是退出python运行环境,说明上下文管理很安全


    2、with语句的使用

    lass Point:
        def __init__(self):
            print('init')
        
        def __enter__(self):
            print('enter')
            return self  #如果没有返回对象,p和f不相等,问题在于__enter__方法,它将自己的返回值赋给f了
        
        def __exit__(self,exc_type,exc_val,exc_tb):
            print('exit')
    
    p = Point()
    with p as f:
        print(p == f)  
        print('do sth.')
    
    __enter__ 方法返回值就是上下文中使用的对象,with语句会把它的返回值赋值给as字句的变量
     


    3、__enter__方法和__exit__方法的参数

    1. __exit__方法有3个参数 __exit__(self,exc_type,exc_val,exc_tb):
    • 这三个参数都与异常有关,如果有异常,参数意义如下
    • exc_type: 异常类型
    • exc_value : 异常的值

    4、为加法函数计时:

    1. 使用装饰器显示该函数的执行时长
    2. 使用上下文管理显示该函数的执行时长
    #1、装饰器实现
    
    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('{} 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))
    
    
    
    # 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('{} took {}s'.format(fn.__name__,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
        
        def __exit__(self,exc_type,exc_val,exc_tb):
            self.delta = (datetime.datetime.now() - self.start).total_seconds()
            print('{} took {}s. context'.format(self.fn.__name__,self.delta))
            pass
                  
        
        def __call__(self,x,y):
            print(x,y)
            return self.fn(x,y)
            
    
    with TimeIt(add) as foo:            
        foo(4,5)


    5、根据上面的代码,能不能把类当做装饰器用

    import time
    import datetime
    from functools import wraps
    
    
    
    class TimeIt:
        '''This is A Class'''  
        def __init__(self,fn=None):
            print(11111111111)
            if fn is not None:          
                self.fn = fn
                #把函数对象的文档字符串赋给类
                wraps(fn)(self)
        
        def __enter__(self):
            print(22222222222)
            self.start = datetime.datetime.now()
            return self
        
        def __exit__(self,exc_type,exc_val,exc_tb):
            print(33333333333)
            self.delta = (datetime.datetime.now() - self.start).total_seconds()
            print('{} took {}s. context'.format(self.fn.__name__,self.delta))
            pass
                  
        
        def __call__(self,*args,**kwargs):
            print(444444444444)
            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  
    def add(x,y):
        ''' This is add function'''
        time.sleep(2)
        return x + y
    
    add(4,5)
    print(add.__doc__)
    print(TimeIt().__doc__)


    6、上下文应用的场景

    •  1、增强功能:在代码执行的前后增加代码,以增强其功能,类似装饰器的功能
    •  2、资源管理:打开了资源需要关闭,例如文件对象,网络连接,数据库连接等
    •  3、权限验证:在执行代码之前,做权限的验证,在__enter__中处理

    7、contextlib.contextmaneger

    • 它是一个装饰器实现上下文管理,装饰一个函数,而不用像类一样实现__enter__和exit方法
    • 对下面的函数有要求,必须有yield,也就是这个函数必须返回一个生成器,且只有yield一个值
    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)
    
    #f接收yield语句的返回值,上面的程序看似不错,但是增加一个异常试试,发现不能保证exit执行
    #需要增加异常捕获try finally
    
    总结:如果业务逻辑简单可以使用函数装饰器方式,如果业务复杂,用类的方式加__enter__和__exit__方法方便
  • 相关阅读:
    SpringBoot20 集成SpringSecurity02 -> 利用SpringSecurity进行前后端分离的登录验证
    Angular问题04 模块导入错误???、BrowserModule模块重复加载???、material模块引入后报错
    基于http的多进程并发文件服务器
    准备面试的那些事儿2
    ubuntu中解决/usr/bin/ld: cannot find -lxxx
    kafka学习之相关命令
    linux中制作动态库
    kafka之c接口常用API------librdkafka
    kafka入门:简介、使用场景、设计原理、主要配置及集群搭建(转)
    <c和指针>学习笔记6输入输出函数
  • 原文地址:https://www.cnblogs.com/jiangzuofenghua/p/11448745.html
Copyright © 2020-2023  润新知