Python进阶:自定义对象实现切片功能

2018-12-31 更新声明:切片系列文章本是分三篇写成,现已合并成一篇。合并后,修正了一些严重的错误(如自定义序列切片的部分),还对行文结构与章节衔接作了大量改动。原系列的单篇就不删除了,毕竟也是有单独成篇的做用。特此声明,请阅读改进版—— Python进阶:全面解读高级特性之切片!https://mp.weixin.qq.com/s/IRAjR-KHZBPEEkdiofseGQpython

切片是 Python 中最迷人最强大最 Amazing 的语言特性(几乎没有之一),在《Python进阶:切片的误区与高级用法》中,我介绍了切片的基础用法、高级用法以及一些使用误区。这些内容都是基于原生的序列类型(如字符串、列表、元组......),那么,咱们是否能够定义本身的序列类型并让它支持切片语法呢?更进一步,咱们是否能够自定义其它对象(如字典)并让它支持切片呢?app

一、魔术方法:__getitem__()

想要使自定义对象支持切片语法并不难,只须要在定义类的时候给它实现魔术方法 __getitem__() 便可。因此,这里就先介绍一下这个方法。ide

语法: object.__getitem__(self, key)源码分析

官方文档释义:Called to implement evaluation of self[key]. For sequence types, the accepted keys should be integers and slice objects. Note that the special interpretation of negative indexes (if the class wishes to emulate a sequence type) is up to the __getitem__() method. If key is of an inappropriate type, TypeError may be raised; if of a value outside the set of indexes for the sequence (after any special interpretation of negative values), IndexError should be raised. For mapping types, if key is missing (not in the container), KeyError should be raised.学习

归纳翻译一下:__getitem__() 方法用于返回参数 key 所对应的值,这个 key 能够是整型数值和切片对象,而且支持负数索引;若是 key 不是以上两种类型,就会抛 TypeError;若是索引越界,会抛 IndexError ;若是定义的是映射类型,当 key 参数不是其对象的键值时,则会抛 KeyError 。lua

二、自定义序列实现切片功能

接下来,咱们定义一个简单的 MyList ,并给它加上切片功能。(PS:仅做演示,不保证其它功能的完备性)。翻译

class MyList():
    def __init__(self):
        self.data = []
    def append(self, item):
        self.data.append(item)
    def __getitem__(self, key):
        print("key is : " + str(key))
        return self.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'])

### 输出结果:
key is : 3
Python猫
key is : slice(None, 2, None)
['My', 'name']
key is : hi
Traceback (most recent call last):
...
TypeError: list indices must be integers or slices, not str

从输出结果来看,自定义的 MyList 既支持按索引查找,也支持切片操做,这正是咱们的目的。code

特别须要说明的是,此例中的 __getitem__() 方法会根据不一样的参数类型而实现不一样的功能(取索引位值或切片值),也会稳当地处理异常,因此并不须要咱们再去写繁琐的处理逻辑。网上有很多学习资料彻底是在误人子弟,它们会教你区分参数的不一样类型,而后写一大段代码来实现索引查找和切片语法,简直是多此一举。下面的就是一个表明性的错误示例:component

###略去其它代码####
def __getitem__(self, index):
    cls = type(self)
    if isinstance(index, slice):  # 若是index是个切片类型,则构造新实例
       return cls(self._components[index])
    elif isinstance(index, numbers.Integral):  # 若是index是个数,则直接返回
        return self._components[index]
    else:
        msg = "{cls.__name__} indices must be integers"
        raise TypeError(msg.format(cls=cls))

三、自定义字典实现切片功能

切片是序列类型的特性,因此在上例中,咱们不须要写切片的具体实现逻辑。可是,对于其它非序列类型的自定义对象,就得本身实现切片逻辑。以自定义字典为例(PS:仅做演示,不保证其它功能的完备性):orm

class MyDict():
    def __init__(self):
        self.data = {}
    def __len__(self):
        return len(self.data)
    def append(self, item):
        self.data[len(self)] = item
    def __getitem__(self, key):
        if isinstance(key, int):
            return self.data[key]
        if isinstance(key, slice):
            slicedkeys = list(self.data.keys())[key]
            return {k: self.data[k] for k in slicedkeys}
        else:
            raise TypeError

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 (most recent call last):
...
TypeError

上例的关键点在于将字典的键值取出,并对键值的列表作切片处理,其妙处在于,不用担忧索引越界和负数索引,将字典切片转换成了字典键值的切片,最终实现目的。

四、小结

最后小结一下:本文介绍了__getitem__() 魔术方法,并用于实现自定义对象(以列表类型和字典类型为例)的切片功能,但愿对你有所帮助。

参考阅读:

Python进阶:切片的误区与高级用法

官方文档getitem用法:http://t.cn/EbzoZyp

Python切片赋值源码分析:http://t.cn/EbzSaoZ

相关文章
相关标签/搜索