一、容器相关方法
- __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__方法的参数
- __exit__方法有3个参数 __exit__(self,exc_type,exc_val,exc_tb):
- 这三个参数都与异常有关,如果有异常,参数意义如下
- exc_type: 异常类型
- exc_value : 异常的值
4、为加法函数计时:
- 使用装饰器显示该函数的执行时长
- 使用上下文管理显示该函数的执行时长
#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__方法方便