类和对象12:容器方法

目录

1. 容器协议

2. 定制不可变容器

3. 定制可变容器

4. 迭代器

5. 定制迭代器

__iter__(self) 

__next__(self) 

实操示例


1. 容器协议

Python 中的协议(protocol)与其他编程语言中的接口很相似,规定哪些方法必须被定义,但 Python 中协议更多的是一种指南,而不是强制要求。

在 Python 中序列(如列表、字符串、元组等)和映射(字典)都属于容器类型,和容器类型有关的魔法方如下表:

魔法方法

含义

__len__(self)

在 len(self) 上调用,定义返回容器中元素个数的行为,可能用于真值检测

__contains__(self, item)

在成员测试运算符(in 或 not in)上调用,定义检测容器成员的行为

__iter__(self)

在 iter(self) 上调用,定义容器的迭代器

__next__(self)

在 next(self) 上调用,定义迭代器返回下一个值的行为

__getitem__(self, key)

在 self[key]、self[i:j:k]、x in self 等情况中调用,定义获取容器中指定元素的行为

__setitem__(self, key, value)

在 self[key] = value,self[i:j:k] = value 中调用,定义为容器的键或索引或分片赋值的行为 

__delitem__(self, key)

在 del self[key]、del self[i:j:k] 中调用,定义删除容器中指定元素的行为,相当于 del self[key]

__reversed__(self)

在 reversed() 上调用,定义反向迭代的行为,返回一个新的在容器中以反向顺序迭代所有对象的可迭代对象

当我们需要定制容器时,需要重构上述魔法方法,此时有如下协议:

  • 定制不可变容器,需要定义 __len__ 和 __getitem__ 方法;
  • 定制可变容器,则除了 __len__ 和 __getitem__ 方法外,还需定义 __setitem__ 和 __delitem__ 方法。

2. 定制不可变容器

定制一个不可改变容器,并记录每个序列被读取的次数:

class Mylist:
    def __init__(self, *var):
        self.mylist = list(var)
        self.counts = {}.fromkeys(range(len(self.mylist)), 0)

    def __len__(self):
        mylistlen = len(self.mylist)
        return mylistlen

    def __getitem__(self, item):
        self.counts[item] += 1
        itemvalue = self.mylist[item]
        self.lastinfo = f'容器元素 {itemvalue} 已被读取 {self.counts[item]} 次'
        return itemvalue

listx = Mylist('abc',1,2,[3,'$'])
listx[1]
1
listx[0]
'abc'
len(listx)
4
listx.counts
{0: 1, 1: 1, 2: 0, 3: 0}
listx.lastinfo
'容器元素 abc 已被读取 1 次'

3. 定制可变容器

定制一个可变容器,要求记录列表中每个元素被访问的次数,并在-章节2 定制不可变容器 基础上,增加如下功能

  • 要求1:实现获取、设置和删除一个元素的行为(删除一个元素的时候对应的计数器也会被删除)
  • 要求2:增加 counter(index) 方法,返回 index 参数所指定的元素记录的访问次数
  • 要求3:实现 append()、pop()、remove()、insert()、clear() 和 reverse() 方法
class Mylist:

#初始化,创建列表,创建列表元素访问计数字典
    def __init__(self, *var):
        self.value = list(var)
        self.counts = {}.fromkeys(range(len(self.value)), 0)

#获取容器长度方法
    def __len__(self):
        return len(self.value)

#获取容器元素方法,每次获取元素,该元素的访问计数+1
    def __getitem__(self, item):
        itemvalue = self.value[item]
        self.counts[item] += 1
        return itemvalue

#设置容器元素数值方法
    def __setitem__(self, key, value):
        self.value[key] = value

#删除容器元素方法,同时删除该元素的访问计数
    def __delitem__(self, key):
        del self.value[key]
        del self.counts[key]

#返回元素的访问计数
    def counter(self, index):
        return self.counts[index]

#容器添加元素,新增元素配套增加访问计数器,初始值为0
    def append(self, value):
        self.value.append(value)
        self.counts[len(self.value) - 1] = 0

#容器按索引删除元素,默认删除最后一位元素
#支持索引为负,从后往前删除元素
#删除元素后,被删除元素之后的所有元素,访问计数器跟随元素补位顺序迁移,随后删除多余的元素访问计数
    def pop(self, key=-1):
        delvalue = self.value[key]

        if - len(self.value) <= key <= len(self.value):
            if key < 0:
                key += len(self.value)
            for i in range(key, len(self.value)-1):
                self.counts[i] = self.counts[i + 1]
            del self.counts[len(self.value)-1]
        else:
            raise IndexError('pop index out of range')

        del self.value[key]
        return delvalue

#容器删除第一个值为 value 的元素
#值在容器中,查询值的第一个索引并删除对应元素;值不在容器中,返回指定错误
#被删除元素之后的所有元素,访问计数器跟随元素补位顺序迁移,随后删除多余的元素访问计数
    def remove(self, value):
        try:
            removeindex = self.value.index(value)
            for i in range(removeindex, len(self.value)-1):
                self.counts[i] = self.counts[i + 1]
            del self.counts[len(self.value)-1]
            del self.value[removeindex]
        except:
            raise ValueError('Mylist.remove(x): x not in Mylist')

#容器在索引 index 位置插入值 value,后续元素自动后移一位
#后移的元素的访问计数,跟随元素一并移动
#支持索引为负,从后往前插入;支持索引超过容器长度,直接插入 value 到容器的最前或最后
    def insert(self, index, value):
        if - len(self.value) < index < len(self.value):
            if index < 0:
                index += len(self.value)
            self.value.insert(index, value)
            lastcount = self.counts[index]
            self.counts[index] = 0
            for i in range(index+1, len(self.value)-1):
                nextcount = self.counts[i]
                self.counts[i] = lastcount
                lastcount = nextcount
            self.counts[len(self.value)-1] = lastcount
        else:
            if index < 0:
                self.value = [value] + self.value
                lastcount = self.counts[0]
                self.counts[0] = 0
                for i in range(1, len(self.value) - 1):
                    nextcount = self.counts[i]
                    self.counts[i] = lastcount
                    lastcount = nextcount
                self.counts[len(self.value)-1] = lastcount
            else:
                self.value.append(value)
                self.counts[len(self.value)-1] = 0

#容器元素清空,同时清空容器元素访问计数
    def clear(self):
        self.value.clear()
        self.counts.clear()

#容器元素倒序,容器元素访问计数也跟随倒序移动
    def reverse(self):
        self.value.reverse()
        self.counts = dict(zip(list(self.counts.keys()),list(reversed(self.counts.values()))))

#--------------------------实操验证-----------------------------
#创建实例 list1
list1 = Mylist(3,2,1,0,1,2,3)

#检查实例的值和计数器初始化
list1.value
[3, 2, 1, 0, 1, 2, 3]
list1.counts
{0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0}

#读取实例 list1 各元素,检查计数器
list1[1]
2
list1[2]
1
list1[2]
1
list1[2]
1
list1[4]
1
list1[4]
1
list1[5]
2
list1[6]
3
list1[6]
3
list1.counts
{0: 0, 1: 1, 2: 3, 3: 0, 4: 2, 5: 1, 6: 2}

#按索引删除,检查值和计数器
list1.pop(2)
1
list1.value
[3, 2, 0, 1, 2, 3]
list1.counts
{0: 0, 1: 1, 2: 0, 3: 2, 4: 1, 5: 2}

#按值删除,检查值和计数器
list1.remove(2)
list1.value
[3, 0, 1, 2, 3]
list1.counts
{0: 0, 1: 0, 2: 2, 3: 1, 4: 2}

#清空容器,检查值和计数器
list1.clear()
list1.value
[]
list1.counts
{}

#添加元素,并访问元素,检查值和计数器
list1.append('a')
list1.append(1)
list1.append(2)
list1.append('b')
list1[2]
2
list1[2]
2
list1[1]
1
list1.value
['a', 1, 2, 'b']
list1.counts
{0: 0, 1: 1, 2: 2, 3: 0}

#倒序、正序插入元素,检查值和计数器
list1.insert(-2,'xyz')
list1.value
['a', 1, 'xyz', 2, 'b']
list1.counter(3)
2
list1.counts
{0: 0, 1: 1, 2: 0, 3: 2, 4: 0}

4. 迭代器

迭代是重复反馈过程的活动,每一次对过程的重复称为一次“迭代”,而每一次迭代结果作为下一次迭代的初始值;

提供迭代方法的容器称为可迭代对象,如常见的序列、字典都支持迭代操作,属于可迭代对象,即迭代器;

譬如使用 for 循环对列表进行迭代操作如下:

test = [1,2,3,4,5]
for each in test:
     print(each)
     
1
2
3
4
5

Python 提供两个BIF:iter()、next() 处理迭代操作,调用 iter() 得到它的迭代器,调用 next() 返回迭代器的下一个值,当迭代器没有值可以返回,抛出异常 StopIteration。

#通过 iter() 获取列表 test 的迭代器 testiter 
testiter = iter(test)

#testiter 为列表的迭代器对象
testiter
<list_iterator object at 0x00000160CE20F250>

#通过 next() 获取迭代器的下一个值,直到抛出异常 StopIteration
next(testiter)
1
next(testiter)
2
next(testiter)
3
next(testiter)
4
next(testiter)
5
next(testiter)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
StopIteration

5. 定制迭代器

iter()、next() 对应的魔法方法即 __iter__、__next__;

__iter__(self) 

返回一个带有 __next__ 方法的可迭代对象,一个容器如果本身即迭代器,常返回容器本身;

通过 for 循环、next() 函数等方法调用迭代器的 __next__ 方法获取迭代器范围内的值,直到抛出 StopIteration 异常。

__next__(self) 

__next__ 方法决定了迭代操作的规则,是定制迭代器的关键;

实操示例

定义一个斐波那契数列类型:

class Fibonacci:
    def __init__(self, f1=1, f2=1):
        self.f1 = f1
        self.f2 = f2

    def __iter__(self):
        return self

    def __next__(self):
        temp = self.f1
        self.f1 = self.f2
        self.f2 += temp
        if self.f1 > 100:
            raise StopIteration
        return self.f1

ftest = Fibonacci()
for each in ftest:
     print(each)
     
1
2
3
5
8
13
21
34
55
89
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

燃烧的火鸟啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值