python迭代_编程入门15:Python迭代机制

我们已熟悉了“迭代”这一概念,许多数据类型都支持迭代。可迭代对象的判断依据是看其成员中有没有__iter__,只要对象定义了__iter__方法,我们就能使用iter函数返回对象的“迭代器”(Iterator)——Python迭代操作的统一机制是先把可迭代对象转成迭代器,然后逐个取出迭代器中的元素,如果没有元素可取则停止迭代并抛出StopIteration异常。以下代码演示了如何手动获取并操作迭代器:

In [1]: s = "迭代"

In [2]: hasattr(s, "__iter__") # hasattr函数判断对象有无特定属性

Out[2]: True

In [3]: i = iter(s) # iter函数使用可迭代对象的__iter__方法返回迭代器

In [4]: type(i)

Out[4]: str_iterator

In [5]: next(i) # next函数返回迭代器里的下一个元素

Out[5]: '迭'

In [6]: next(i)

Out[6]: '代'

In [7]: next(i) # 迭代器里的元素耗尽后将停止迭代抛出异常

Traceback (most recent call last):

File "", line 1, in

next(i)

StopIteration

多数时候我们都会使用for语句来进行循环迭代,在解释器内部自动完成上述操作——迭代器是一次性使用的特殊可迭代对象,其中的元素取一个就少一个。以下代码对迭代器使用成员运算符in,同样也是逐个取出元素:

In [8]: i = iter("ab")

In [9]: "a" in i # 取出一个元素即满足条件结束迭代

Out[9]: True

In [10]: "b" in i # 后面的元素还存在

Out[10]: True

In [11]: i = iter("ab")

In [12]: "b" in i # 取出两个元素才满足条件结束迭代

Out[12]: True

In [13]: "a" in i # 前面的元素已取走

Out[13]: False

迭代器一定包含__next__方法,当我们调用next函数时就会执行迭代器的__next__方法。下面让我们尝试定义一个迭代器类,逐个输出2的正整数次幂:

class Power2n:

"""2的正整数次幂数列迭代器类

"""

def __init__(self, n):

self.n = n # 数列长度

self.cur = 1 # 当前幂次

def __iter__(self): # 可迭代对象必须实现__iter__方法来返回迭代器

return self

def __next__(self): # 迭代器必须实现__next__方法来返回下一个元素

if self.n >= self.cur:

result = 2 ** self.cur

self.cur += 1

return result

else: # 没有元素可返回则抛出停止迭代异常

raise StopIteration()

迭代器初始化时不会把所有元素都载入内存,而是等__next__方法被调用时返回一个元素,这样无论要迭代多少次,所消耗的内存空间都保持不变。

迭代器很好用,但定义起来有点繁琐,为此Python又提供了“生成器”(Generator)——同样输出2的正整数次幂数列,只需如下的生成器函数:

def Power2nX(n):

"""2的正整数次幂数列生成器函数

"""

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

yield 2 ** i

可以看到生成器函数很像普通函数,只是改用yield关键字而非return来返回值,这样返回的就是一个生成器对象。生成器是特殊的迭代器,会自动实现迭代方法,并自动处理迭代异常。当调用生成器的__next__方法时,将执行对应生成器函数到yield语句返回一个值,下次调用时会从离开位置之后继续执行返回下一个值。

生成器函数已经相当简洁,不过Python还提供了更为紧凑的“解析式”(Comprehension)语法,基于可迭代对象经过简单运算推导出新的列表或者生成器——所以想要输出2的正整数次幂数列,其实只要一行语句就够了:

In [14]: import sys # 标准库系统模块

In [15]: l = [2**n for n in range(1, 11)] # 列表解析式

In [16]: l

Out[16]: [2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]

In [17]: sys.getsizeof(l) # 查看对象占用字节数

Out[17]: 192

In [18]: g = (2**n for n in range(1, 11)) # 生成器解析式

In [19]: type(g)

Out[19]: generator

In [20]: sys.getsizeof(g)

Out[20]: 120

In [21]: l = [2**n for n in range(1, 21)]

In [22]: g = (2**n for n in range(1, 21))

In [23]: sys.getsizeof(l)

Out[23]: 264

In [24]: sys.getsizeof(g) # 生成器对象大小是固定的

Out[24]: 120

可以看到列表会随元素的增加而消耗更多内存,生成器的大小则保持不变,需要迭代海量数据时用生成器更合适。

以下是一段绘制曼德布罗分形图的程序:

"""xiter_mandelbrot.pyw 绘制曼德布罗分形图

"""

import tkinter as tk

import time

def mandelbrot_pixel(c):

"""返回曼德布罗平面像素点对应索引号

"""

maxiter = 256

z = complex(0.0, 0.0)

for i in range(maxiter):

z = z * z + c

if abs(z) >= 2.0:

return i

return 256

def mandelbrot_image(xa, xb, ya, yb, x, y):

"""返回曼德布罗平面图像字符串

"""

clr = ["#%02x%02x%02x" % ( # 索引号0-255对应不同颜色

int(255 * ((i / 255) ** 8)) % 64 * 4,

int(255 * ((i / 255) ** 8)) % 128 * 2,

int(255 * ((i / 255) ** 8)) % 256) for i in range(255, -1, -1)]

clr.append("#000000") # 索引号256对应黑色

# 计算复平面坐标对应的像素点

xm = [xa + (xb - xa) * kx / x for kx in range(x)]

ym = [ya + (yb - ya) * ky / y for ky in range(y)]

# 生成图像字符串

return " ".join((("{" + " ".join(clr[mandelbrot_pixel(complex(i, j))]

for i in xm)) + "}" for j in ym))

def main():

"""绘制曼德布罗分形图

"""

# 复数取值范围

xa = -2.25

xb = 0.75

ya = -1.25

yb = 1.25

# 显示窗口大小

x = 600

y = 500

window = tk.Tk()

canvas = tk.Canvas(window, width=x, height=y, bg="#000000")

canvas.pack()

t1 = time.process_time()

img = tk.PhotoImage(width=x, height=y)

canvas.create_image((0, 0), image=img, state="normal", anchor=tk.NW)

# 计算并显示图像

pixels = mandelbrot_image(xa, xb, ya, yb, x, y)

img.put(pixels)

print("运行耗时:{}秒。".format(time.process_time() - t1))

tk.mainloop()

if __name__ == "__main__":

main()

以上程序用到了复数类型、列表解析式和生成器解析式,并引入time模块来查看运行耗时,绘图区30万像素点的颜色需要逐一计算,每个点执行最多256次迭代,在我的i3-6100电脑上需要花费3秒钟……

15_mandelbrot.png

——编程原来是这样……

编程小提示:曼德布罗集合

“曼德布罗集合”(Mandelbrot Set)是在自平方变换 fc(z) = zn2 + c 下不发散的复数值 c 的集合:对于复平面上的一点 c,从 z=0 开始对 fc(z) 进行迭代:zn+1 = zn + c (n = 0, 1, 2, ...)。重复迭代步骤以确定结果是否收敛(例如迭代256次后复数绝对值即与原点的距离不大于2),这个收敛域就是曼德布罗集合——曼德布罗集合的主要部分包含在实部-2.25至0.75,虚部-1.25至1.25的复平面区域中。

曼德布罗集合是最令人着迷的分形图之一,很难想象如此简单的公式能产生如此复杂的图形,无论如何放大也无法穷尽其所包含的细节,更多介绍可参看维基百科 https://en.wikipedia.org/wiki/Mandelbrot_set

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值