Python进阶之自定义对象实现切片功能
切片是Python中最迷人最强大最Amazing的语言特性(几乎没有之一),在《Python进阶:切片的误区与高级用法》中,我介绍了切片的基础用法、高级用法以及一些使用误区。这些内容都是基于原生的序列类型(如字符串、列表、元组......),那么,我们是否可以定义自己的序列类型并让它支持切片语法呢?更进一步,我们是否可以自定义其它对象(如字典)并让它支持切片呢?
1、魔术方法:__getitem__()
想要使自定义对象支持切片语法并不难,只需要在定义类的时候给它实现魔术方法__getitem__()即可。所以,这里就先介绍一下这个方法。
语法:object.__getitem__(self,key)
官方文档释义:Calledtoimplementevaluationofself[key].Forsequencetypes,theacceptedkeysshouldbeintegersandsliceobjects.Notethatthespecialinterpretationofnegativeindexes(iftheclasswishestoemulateasequencetype)isuptothe__getitem__()method.Ifkeyisofaninappropriatetype,TypeErrormayberaised;ifofavalueoutsidethesetofindexesforthesequence(afteranyspecialinterpretationofnegativevalues),IndexErrorshouldberaised.Formappingtypes,ifkeyismissing(notinthecontainer),KeyErrorshouldberaised.
概括翻译一下:__getitem__()方法用于返回参数key所对应的值,这个key可以是整型数值和切片对象,并且支持负数索引;如果key不是以上两种类型,就会抛TypeError;如果索引越界,会抛IndexError;如果定义的是映射类型,当key参数不是其对象的键值时,则会抛KeyError。
2、自定义序列实现切片功能
接下来,我们定义一个简单的MyList,并给它加上切片功能。(PS:仅作演示,不保证其它功能的完备性)。
classMyList(): def__init__(self): self.data=[] defappend(self,item): self.data.append(item) def__getitem__(self,key): print("keyis:"+str(key)) returnself.data[key] l=MyList() l.append("My") l.append("name") l.append("is") l.append("Python猫") print(l[3]) print(l[:2]) print(l['hi'])
###输出结果:
keyis:3
Python猫
keyis:slice(None,2,None)
['My','name']
keyis:hi
Traceback(mostrecentcalllast):
...
TypeError:listindicesmustbeintegersorslices,notstr
从输出结果来看,自定义的MyList既支持按索引查找,也支持切片操作,这正是我们的目的。
特别需要说明的是,此例中的__getitem__()方法会根据不同的参数类型而实现不同的功能(取索引位值或切片值),也会妥当地处理异常,所以并不需要我们再去写繁琐的处理逻辑。网上有不少学习资料完全是在误人子弟,它们会教你区分参数的不同类型,然后写一大段代码来实现索引查找和切片语法,简直是画蛇添足。下面的就是一个代表性的错误示例:
###略去其它代码#### def__getitem__(self,index): cls=type(self) ifisinstance(index,slice):#如果index是个切片类型,则构造新实例 returncls(self._components[index]) elifisinstance(index,numbers.Integral):#如果index是个数,则直接返回 returnself._components[index] else: msg="{cls.__name__}indicesmustbeintegers" raiseTypeError(msg.format(cls=cls))
3、自定义字典实现切片功能
切片是序列类型的特性,所以在上例中,我们不需要写切片的具体实现逻辑。但是,对于其它非序列类型的自定义对象,就得自己实现切片逻辑。以自定义字典为例(PS:仅作演示,不保证其它功能的完备性):
classMyDict(): def__init__(self): self.data={} def__len__(self): returnlen(self.data) defappend(self,item): self.data[len(self)]=item def__getitem__(self,key): ifisinstance(key,int): returnself.data[key] ifisinstance(key,slice): slicedkeys=list(self.data.keys())[key] return{k:self.data[k]forkinslicedkeys} else: raiseTypeError d=MyDict() d.append("My") d.append("name") d.append("is") d.append("Python猫") print(d[2]) print(d[:2]) print(d[-4:-2]) print(d['hi'])
###输出结果:
is
{0:'My',1:'name'}
{0:'My',1:'name'}
Traceback(mostrecentcalllast):
...
TypeError
上例的关键点在于将字典的键值取出,并对键值的列表做切片处理,其妙处在于,不用担心索引越界和负数索引,将字典切片转换成了字典键值的切片,最终实现目的。
4、小结
最后小结一下:本文介绍了__getitem__()魔术方法,并用于实现自定义对象(以列表类型和字典类型为例)的切片功能,希望对你有所帮助。也希望大家多多支持毛票票。