6,模拟篇
- __call__
- __len__
- __length_hint__
- __getitem__
- __setitem__
- __delitem__
- __reversed__
- __contains__
- __iter__
- __missing__
- __enter__和__exit__
__call__方法
所谓的callable就是可以以函数调用的形式来使用的对象,那想让一个类的对象成为callable,我们需要给它定义这个__call__ method,下面我们建立一个Multiplier的object o 并且让它的乘法倍数是3,然后我们可以像使用函数一样使用这个对象o,那么它实际上调用的就是这个__call__ method ,我们给进的参数4,就被传到了这个arg里,所以我们理论上该返回一个3乘以4就是12
class Multiplier: def __init__(self, mul): self.mul = mul def __call__(self, arg): return self.mul * arg o = Multiplier(3) print(o(4)) # 12
__len__方法
接下来我们看emulate container type,所谓container就是容器,在python中我们最常见的容器是list跟Dictionary,它们呢也分别对应了两个类别,一个叫sequence一个叫mapping。下面我们写了一个套皮list,我们要看的魔术方法是__len__,它要返回你这个container的长度,这里我直接借用list实现,返回我这个self.data的长度,我们建立了一个MyList object然后打印它的长度就是2,这个__len__最主要的作用呢就是被这个方法builtin方法len调用。
class MyList: def __init__(self, data): self._data = data def __len__(self): return len(self._data) x = MyList([1, 2]) print(len(x)) # 2
如果你这个object没有被定义__boo__这个魔术方法,当你把它当作一个条件去判断的时候,它就会尝试去找这个__len__魔术方法,如果他的长度是0认为是False,否则认为是True,这个特性和我们所有builtin的container都是一样的,我们平常也会写if lst if dct,我们运行下面的代码是有输出的,那如果我们把这个MyList初始化为空呢它就没有了
class MyList: def __init__(self, data): self._data = data def __len__(self): return len(self._data) x = MyList([1, 2]) if x: print("Yeah") # Yeah
__length_hint__方法
说完__len__我们稍微提一下这个__length_hint__,__length_hint__是返回一个估计的长度,它是被这个operator.length_hint调用的,我们运行一下,结果是2,但是这个magic method实际上是很少用到的
import operator class MyList: def __init__(self, data): self._data = data def __length_hint__(self): return len(self._data) x = MyList([1, 2]) print(operator.length_hint(x)) # 2
__getitem__方法
__getitem__就是你用方括号的时候会调用的这么一个魔术方法,比如让你打印x[0]的时候,这个0就会被放到key里传进来,这里的结果就是返回它的index为0的那个值,运行结果是1
class MyList: def __init__(self, data): self._data = data def __getitem__(self, item): return self._data[item] x = MyList([1, 2]) print(x[0]) # 1
我们做一个小小的改变,有的时候我们拿到的数据这个0是一个string,如果我们不小心把这个string作为index传进去了,那list是没有办法认出来这种index的,所以它就会报错
class MyList: def __init__(self, data): self._data = data def __getitem__(self, item): return self._data[item] x = MyList([1, 2]) print(x["0"])
TypeError: list indices must be integers or slices, not str
这个时候我们可以在我们这个类的__getitem__里面,把这个key先转化成int,那这样一来就算传进来的key是一个string,它也能正确的返回这个值了
class MyList: def __init__(self, data): self._data = data def __getitem__(self, item): return self._data[int(item)] x = MyList([1, 2]) print(x["0"]) # 1
__setitem__方法
当我们用方括号去读取一个值的时候使用的是__getitem__,但是当我们用方括号去赋值的时候调用的就是__setitem__,例如给x[0]赋值为3,然后打印x[0],那么运行结果就是3。
class MyList: def __init__(self, data): self._data = data def __getitem__(self, item): return self._data[int(item)] def __setitem__(self, key, value): self._data[key] = value x = MyList([1, 2]) x[0] = 3 print(x["0"]) # 3 x[3] = 4 print(x["0"])
IndexError: list assignment index out of range
那比如说有时候我们给这个list赋值它超出了这个index,比如x[3] = 4,它就会报错,如果我希望这个list,可以在这个Index错误的时候直接无视它不要管它,我们就可以把这个__setitem__稍微改一下,如果出现IndexError直接pass算了,这个时候当我给x[3]赋值它就当无事情发生了
class MyList: def __init__(self, data): self._data = data def __getitem__(self, item): return self._data[int(item)] def __setitem__(self, key, value): try: self._data[key] = value except IndexError: pass x = MyList([1, 2]) x[3] = 4 print(x["0"]) # 1
__delitem__方法
__delitem__就是删除一个key,就是被del这个关键字触发的,x本来是[1, 2, 3]然后把x[1]删除掉,再打印x[1],由于原来的x[1]这个2被删除掉了,现在的x[1]就是3了
class MyList: def __init__(self, data): self._data = data def __getitem__(self, item): return self._data[int(item)] def __setitem__(self, key, value): try: self._data[key] = value except IndexError: pass def __delitem__(self, key): self._data = self._data[0: key] + self._data[key+1:] x = MyList([1, 2, 3]) del x[1] print(x[1]) # 3
__reversed__方法
__reversed__会被python的builtin function reversed调用,reverse平时就是把这个东西反过来
class MyList: def __init__(self, data): self._data = data def __getitem__(self, item): return self._data[int(item)] def __setitem__(self, key, value): try: self._data[key] = value except IndexError: pass def __delitem__(self, key): self._data = self._data[0: key] + self._data[key+1:] def __reversed__(self): return MyList(self._data[::-1]) x = MyList([1, 2, 3]) print(reversed(x)._data) # [3, 2, 1]
__contains__方法
__contains__方法它是当你使用in这个操作的时候被调用的
... def __contains__(self, item): return item in self._data x = MyList([1, 2, 3]) print(2 in x) print(5 in x) # True # False
__iter__方法
__iter__是返回这个container的一个iterator,这里我们就直接把保存的这个_data的iterator给返回回去,这样我们在做for i in x的时候,我们实际上迭代的就是里面的那个list,可以看到打印出来的就是1,2,3
... def __iter__(self): return iter(self._data) x = MyList([1, 2, 3]) for i in x: print(i) # 1 # 2 # 3
__missing__方法
container里面有一个稍微特殊点的的magic method叫做__missing__,这个__missing__只有在dict的subclass里面才有用,也就是你新建的这个class,必须要继承它builtin的这个dict,它的作用是当你想在这字典里找一个key,但找不着的时候它应该干嘛,比如下面我们新建了一个字典之后我们想找d[0],但是我们知道这个字典里面现在一个key都没有,那当这个字典找不到这个key的时候,它就会把这个key仍到__missing__里面来问应该怎么办,这里我们统一返回0,可以看到那这种情况下d[0]就是0了
class MyDict(dict): def __missing__(self, key): return 0 d = MyDict() print(d[0])
__enter__和__exit__方法
说完了container,我们介绍一下这个context type,我们平常在写python的时候经常会用到with什么东西,这个with后面的东西就叫做context,想自定义一个context,我们需要定义它的__enter__和__exit__ magic method,下面我们做了一个最简单的Timer,也就是在当进入这个context的时候,记录一下时间,再出去这个context的时候打印一下这个context里面花了多少时间
import time class Timer: def __enter__(self): self.start = time.time() def __exit__(self, exc_type, exc_val, exc_tb): print(f"T: {time.time()- self.start}") with Timer(): _ = 1000 * 1000 # T: 0.0
有的时候我们会遇见这种with Timer() as t的用法,这个as t 就是把__enter__这个的函数的返回值给保存到 t 里面,在这个部分最常见的操作就是直接把self给返回回去,比如我们就这样做然后在里面打印一下t.start,可以看到把这个context的起始时间就给打印出来了
import time class Timer: def __enter__(self): self.start = time.time() return self def __exit__(self, exc_type, exc_val, exc_tb): print(f"T: {time.time()- self.start}") with Timer() as t: print(t.start) _ = 1000 * 1000
1689503740.810218 T: 0.0
那你可能会发现这个__exit__函数里面还有一些其他的argument,它们分别是exc_type、exc_value还有traceback,这几个东西呢主要是在出现exception的时候使用到,比如我们把刚才那个操作变成1000 / 0 ,然后我们把这几个argument都打印出来,可以看到exception type是zero division error,exception value是division by zero,然后这个traceback是一个object,注意这里我们依然把这个程序跑的时间给记录下来了,然后python才报了错,也就是说即便在这个context里面它raise了一个exception,我timer里__exit__函数的全部内容依然被执行了,所以这个context非常适合拿来释放资源,比如我们常常鼓励大家在打开文件的时候使用with,它就可以防止你忘记了把这个文件关上,同时就算程序报错了你依然可以把这个文件成功的关上
import time class Timer: def __enter__(self): self.start = time.time() return self def __exit__(self, exc_type, exc_val, exc_tb): print(exc_type, exc_val, exc_tb) print(f"T: {time.time()- self.start}") with Timer() as t: _ = 1000 / 0
标签:__,MyList,python,self,魔术,def,._,data,模拟 From: https://www.cnblogs.com/jiushao-ing/p/17557340.html