python将list转换为迭代器代码_Python中的类序列化,迭代器和生成器

本书同名免费MOOC版权声明:本文内容引用自作者的图书《Python编程基础及应用》(高等教育出版社)。本文可以在互联网上转载传播,但必须包含文中的版权声明;本文不可以以纸质出版为目的进行摘抄或改编。

附录A.1. Python中的类序列化、迭代器及生成器

本章的内容有实践价值,但稍稍有点深入,在简单的应用程序实现当中也不是非用不可,跟后续章节关联度也很低。心急的读者可以先略过不读。

对于list、tuple、str这样的类型,我们可以通过[]来访问其特定下标的元素(item);可以通过len()函数来询问其内部包含的元素个数;还可以通过del来删除指定位置的元素。

我们能否设计一个类,让它象序列一样工作呢? 或者说,我们能否实现一个可以当序列用的自定义类型?通过实现一些特殊方法,可以办到。

A.1.1 斐波那契数列

斐波那契数列 - Fibonacci sequence也叫兔子序列,最早用于预测一定周期后野外的兔子的数量。该序列可用一个分段递归函数来描述。

简单计算可知,F(1) = 1, F(2) = 1, F(3) = 2, F(4) = 3, 5, 8,13,21... 简单地说,从第三项开始,每一项等于前两项的和。

A.1.2 斐波那契序列类

下述代码通过实现几个特殊方法实现了序列化的斐波那契类。

#fibseq1.py

class Fibonacci:

def __init__(self):

self.seq = [0,1,1] #序列第1项,第2项为1

self.maxKey = 10000

def computeTo(self,key):

for idx in range(len(self.seq), key + 1):

v = self.seq[idx - 1] + self.seq[idx - 2]

self.seq.append(v)

def __getitem__(self,key):

if not isinstance(key,int): #判断是否int类型

raise TypeError

if key <=0 or key > self.maxKey: #数列不含第0项,最大10000项

raise IndexError

if key > len(self.seq):

self.computeTo(key) #计算序列的前key项

return self.seq[key]

def __setitem__(self,key,value): #key为下标,value为值

if not isinstance(key,int): #判断是否int类型

raise TypeError

if key <=0 or key > self.maxKey: #数列不含第0项,最大10000项

raise IndexError

if key > len(self.seq):

self.computeTo(key) #计算序列的前key项

self.seq[key] = value

def __len__(self):

return self.maxKey #返回最大项数10000作为长度

f = Fibonacci() #实例化Fibonacci类

print("f[20]=",f[20]) #取值,导致f.__getitem__(20)被执行

f[10] = "熊孩子" #赋值,导致f.__setitem__(10,"熊孩子")被执行

for i in range(1,21):

print(f[i],end=",") #取值,导致f.__getitem__(i)被执行

print("") #换行

print("Length of f:",len(f)) #len(f)导致f.__len__()被执行

执行结果

f[20]= 6765

1, 1, 2, 3, 5, 8, 13, 21, 34, 熊孩子, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765,

Length of f: 10000

斐波那契数列这么设计,并没有多大的实践意义。这样做,是希望以读者熟悉的方式向大家介绍序列化类型的方法。当我们以f对象为参数执行len()函数时,f对象的__len__()函数会被执行。斐波那契数列是个无穷数列,这里,作者规定了一个极限,最多10000项。

当我们对f[20]进行取值时,f对象的__getitem__()函数将被执行,key,即下标参数为20。该函数内,首先通过isinstance判断key是否为int,如果不是,引发TypeError异常。然后,判断key的取值,如果<=0或者>10000,引发IndexError异常。接下来,检查key所对应的斐波那契项是否已计算过,如果没有,执行computeTo()成员函数逐项计算。最后,返回self.seq[key]作为结果。

当我们对f[10]进行赋值时,f对象的__setitem__()特殊方法被执行。该方法先对key进行类型和下标取值检查,并确保序列已计算至第key项。然后对self.seq[key]进行赋值。当然,真实的斐波那契数列是不需要进行赋值的,所以这里给了一个“熊孩子”的值以吸引读者的注意:请数一数,确认“熊孩子”在序列中的下标。

由于不准备支持对对象内元素的删除,这里没有实现__delitem__()特殊方法。

可以想象,对f[0]的访问将触发IndexError异常,对f["ABC"]的访问将触发TypeError异常。 此外,缺乏编程训练的读者可能对computeTo()函数的实现感到疑惑。设定一个比较小的key值,比如5,把自己当成计算机,拿一张纸,一支笔,人肉模拟一下代码的执行过程,是理解复杂程序的好办法。

A.1.3 从list继承

面向对象的方法论告诉我们,从一个类那里继承出一个子类,子类将自动获得父类的全部特性。那么,如果从一个序列类继承,继承类不就自动是一个序列类型了吗? 这个思路可以有。

#userlist1.py

class UserList(list):

pass

a = UserList()

a.extend([0,1,2,3,4,5,6,7,8,9])

a[3] = "熊孩子"

print("a[2]=",a[2],"len(a)=",len(a))

print("a[3]=",a[3])

执行结果

a[2]= 2 len(a)= 10

a[3]= 熊孩子

看上去,UserList用起来跟list一模一样。这没有意义,我们通过继承设计新的类,总是要跟父类有些区别。我们来给UserList加点功能。

#userlist2.py

class UserList(list):

def __init__(self,*args): #args吸收除self之外的全部参数

super().__init__(*args) #执行父类的构造函数

self.iCounter = 0

def __getitem__(self, idx): #该函数在应用[]按下标取值时被执行

self.iCounter += 1

return super().__getitem__(idx)

a = UserList()

a.extend([0,1,2,3,4,5,6,7,8,9])

a[3] = "熊孩子"

print("a[2]=",a[2],"len(a)=",len(a))

print("a[3]=",a[3])

print("a.iCounter=",a.iCounter)

执行结果

a[2]= 2 len(a)= 10

a[3]= 熊孩子

a.iCounter= 2

容易看出,通过重载list父类的__getitem__()函数,UserList可以对列表[]取值的次数进行计数。在本例中,a[2],a[3]两次取值,故iCounter值为2。同样地,tuple,str,bytearray这些序列类都可以被继承,实现类似目的。

A.1.4 可迭代Fibonacci数列

列表,元组,数值列表(range list),字典等都是可迭代的(iterable)。我们可以使用for x in X来列举可迭代对象X内部的全部元素。下面我们将把斐波那契数列类可迭代化。直接上代码:

#fibiter.py

class Fibonacci:

def __init__(self, max):

self.a = 1

self.b = 1

self.idx = 0

self.maxIdx = max

def __iter__(self):

return self

def __next__(self):

self.idx += 1

if self.idx == 1:

return 1

elif self.idx == 2:

return 1

elif self.idx > self.maxIdx:

raise StopIteration

else:

c = self.a + self.b

self.a, self.b = self.b, c

return c

for x in Fibonacci(10):

print(x, end=",")

print("")

it = Fibonacci(10)

for x in range(10):

print(next(it), end=",")

print("")

print(list(Fibonacci(10)))

执行结果

1,1,2,3,5,8,13,21,34,55,

1,1,2,3,5,8,13,21,34,55,

[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

Python解释器内部迭代一个对象的过程大致可以描述成下述模样:当代码试图迭代一个对象时,解释器会调用该对象的__iter__()特殊方法,试图获得一个可迭代对象 - iterable object。这个可迭代对象预期应该实现了__next__()方法,每执行一次该方法,就会返回一个“内部”元素,当可迭代对象的“内部”元素已经全部列举完毕后,__next__()方法引发一个StopIteration异常,外部程序捕获该异常后,停止迭代。在这一过程中,StopIteration导常的捕获是由解释器自动进行的,编程者无法也不必进行干预。

解释器对上述__iter__()及__next__()特殊方法的调用也是隐含的,程序员无法也不必要干预。

在上述代码中,Fibonacci类的__iter__()函数返回对象自身做为可迭代对象。Fibonacci对象内部的idx属性记录了当前已经被列举的数列项数,外部程序每次执行__next__()函数时,该函数会生下一项并返回。提示,a,b属性记录了最近两项被列举的数列项的值,而根据Fibonacii的定义,下一项正好等于 a + b。

Fibonacci是无穷数列,但迭代必须有尽头。所以上述代码设定了一个self.maxIdx属性,当已迭代项数达到self.maxIdx时,触发StopIteration异常,终止迭代。

上述代码首先用for循环来迭代Fibonacci(10)对象。然后,代码又连续 10次执行next(it),而每次next(it)的执行,都间接调用执行it.__next__()特殊方法,这算是一种手工迭代方法。最后,代码将Fibonacci(10)转换成列表,这也是一种间接迭代可迭代对象的方法。

A.1.5 生成器

还是Fibonacci,毕竞我们这里要讨论是Python,而不是数学,所以我们尽量使用大家熟悉的数学工具。

#fibgrr.py

def FibonacciGenerator(n):

assert n > 2 #为代码简单,作者要求n>2

print(1,end=",")

print(1,end=",")

a = b = 1

for i in range(3,n+1):

c = a + b

a,b = b,c

print(c,end=",")

FibonacciGenerator(10)

执行结果

1,1,2,3,5,8,13,21,34,55,

这段代码很简单,生成并打印Fibonacci数列的前十项,n代表要打印的项数。在之前,我们已经学会了将一个Fibonacci类对象变成可迭代对象的方法。那么这里,负责生成Fibonacci数列的是一个函数,能否也将这个函数变成可迭代的呢?我们要一项,它就给一项? 解决方法很简单,把print()变成yield即可。

#fibfuncitr.py

def FibonacciGenerator(n):

assert n > 2 # 为代码简单,作者要求n>2

yield 1

yield 1

a = b = 1

for i in range(3, n + 1):

c = a + b

a, b = b, c

yield c

print(list(FibonacciGenerator(10)))

for x in FibonacciGenerator(10):

print(x, end=",")

print("") # 换行

g = FibonacciGenerator(10)

for i in range(10):

print(next(g), end=":")

print("")

print(FibonacciGenerator)

print(g)

执行结果

[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

1,1,2,3,5,8,13,21,34,55,

1:1:2:3:5:8:13:21:34:55:

我们看到了执行结果和相关用法,跟上节所描述的可迭代对象十分相似。上述代码中的FibonacciGenerator(10)函数可以认为是一个生成器。所谓生成器,就是一段伪装成“序列”的“程序”,它是可迭代的。外部程序直接或间接地通过next()函数列举生成器的元素时,这段“程序”就会被执行,它通过yield语句向外部程序提供一个“内部”元素。每次执行yield,都会导致生成器“程序”被挂起暂停,直到外部程序试图列举生成器的下一个元素时,生成器“程序”从断点处继续执行。生成器程序全部执行完成会执行return语句,这意味着它已列举完全部“内部”元素,迭代将停止。这个函数的最后作者没有写出那个return语句,但可以认为解释器会自动加上一个。

FibonacciGenerator就是一个函数对象。由于这个函数对象内部包括yield关键字,所以该函数被“执行”时并不会立即执行,而是返回一个生成器对象,上面的执行结果证明了这一点。print(g)的输出结果为\。当这个生成器对象被外部程序迭代时,其中的代码才会真正运行。

上述的生成器是一个函数对象,还有更简单的:

#simplegrr.py

g1 = [x**2 for x in range(1,10,2)]

g2 = (x**2 for x in range(1,10,2))

print(type(g1),g1)

print(type(g2),list(g2))

执行结果

[1, 9, 25, 49, 81]

[1, 9, 25, 49, 81]

g1的格式我们已经见过多次,这是所谓的列表推导,它生成一个结果列表。而生成全部的列表,将花费大量的CPU时间和内存空间。g2跟g1的区别在于没有使用列表推导的方括号,而是使用圆括号。所以,g2的类型是,它的本质是生成器,也就是伪装成“序列”的程序,只有外部程序试图迭代g2内的元素时,该程序才会真正执行。list(g2)完成了对生成器g2的迭代,生成一个包含g2内全部元素的列表。

本文节选自作者的B站MOOC及同名教材:Python编程基础及应用 — 重庆大学 高等教育出版社,作者亲授_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili​www.bilibili.com

练习

A.1-1 设计一个生成器函数,该生成器函数生成字母'a', 'b','c','d'的全排列,其生成顺序按照字母表排序。用一个for循环迭代这个生成器函数,打印全部元素。

A.1-2 设计一个序列化类。实例化该类并使用下标访问其全部元素。其中,第i个元素的值为斐波那契序列前i项的和,i的最大值限定为30。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值