编程入门15:Python迭代机制

上一篇:编程入门14: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 "<ipython-input-7-a883b34d6d8a>", line 1, in <module>
    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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,812评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,626评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,144评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,052评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,925评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,035评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,461评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,150评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,413评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,501评论 2 307
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,277评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,159评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,528评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,868评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,143评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,407评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,615评论 2 335

推荐阅读更多精彩内容