《Fluent Python》第14章 可迭代的对象、迭代器和生成器

前言 迭代是数据处理的基石。扫描内存中放不下的数据集时,我们要找到一种惰性获取数据项的方式,即按需一次获取一个数据项。这就是迭代器模式(Iterator pattern) 所有生成器都是迭代器,因为生成器完全实现了迭代器接口 在Python3 中,生成器有广泛的用途。现在,即使是内置的range()函数也返回一个类似生成器的对象,而以前则返回完整的列表 iter() 解释器需要迭代对象x时,会自动调用iter(x). 内置的iter函数有以下作用: 检查对象是否实现了__iter__(),如果实现了就调用它,从而获取一个迭代器 当__iter__()不存在时转而调用__getitem__(), python会创建一个迭代器,尝试按顺序(从索引0开始)获取元素 抛出TypeError, 提示object is not iterable 可迭代的对象与迭代器的对比 下面是一个简单的 for 循环,迭代一个字符串。这里,字符串ABC 是可迭代的对象。背后是有迭代器的,只不过我们看不到: 1 2 3 4 5 6 s = 'ABC' for ch in s: print(s) >>> A >>> B >>> C 若使用while, 使用可迭代的对象构建迭代器it 1 2 3 4 5 6 7 8 9 10 11 s = 'ABC' it = iter(s) while True: try: print(next(it)) except StopIteration: del it break >>> A >>> B >>> C 标准的迭代器接口有两个方法:...

created: 2023-04-04  |  updated: 2023-04-04  |  阿秀

《Fluent Python》第15章 上下文管理器和else块

前言 with 语句会设置一个临时的上下文,交给上下文管理器对象控制,并且负责清理上下文。这么做能避免错误并减少样板代码,因此 API 更安全,而且更易于使用。除了自动关闭文件之外,with 块还有很多用途。 else for/else 仅当for循环运行完毕时(即for循环没有被break语句中止)才运行else块。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 for x in range(3): print(x) if x > 100: break else: print('x never greater than 100') >>> 0 >>> 1 >>> 2 >>> x never greater than 100 for x in range(5): print(x) if x > 2: break else: print('I am a dog') >>> 0 >>> 1 >>> 2 >>> 3 while/else 仅当while循环因为条件为假值而退出时(即while循环没有被break语句中止)才运行else块。...

created: 2023-04-04  |  updated: 2023-04-04  |  阿秀

《Fluent Python》第16章 协程

前言 在理解协程时要从根本上把yield视作控制流程的方式, 这样就好理解协程了 生成器是如何进化成协程的 协程的底层架构在PEP 342—Coroutines via Enhanced Generators中定义。 在Python 2.5(2006年)实现了。自此之后, yield关键字可以在表达式中使用,而且生成器API中增加了send(value)方法。生成器的调用方可以使用send(...)方法发送数据, 发送的数据会成为生成器函数中yield表达式的值。因此,生成器可以作为协程使用。协程是指一个过程,这个过程与调用方协作, 产出由调用方提供的值。 除此之外,PEP 342还添加了throw(...)和close()方法:前者的作用是让调用方抛出异常,在生成器中处理;后者的作用是终止生成器。 PEP 380对生成器函数的句法做了两处改动: 生成器可以返回一个值;(在这之前,如果在生成器中给return语句提供值, 会抛出SyntaxError异常。 新引入了yield from句法, 使用它可以把复杂的生成器重构成小型的嵌套生成器,省去了之前把生成器的工作委托给子生成器所需的大量样板代码 用作协程的生成器的基本行为 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def simple_coroutine(): print('-> coroutine started...') x = yield print('-> coroutine received...', x) c = simple_coroutine() print(c) >>> <generator object simple_coroutine at 0x7f8d60c7d468> next(c) # c....

created: 2023-04-04  |  updated: 2023-04-04  |  阿秀

《Fluent Python》第17章 使用concurrent.futures处理并发

网络下载的三种风格 为了高效处理网络I/O, 需要使用并发, 因为网络有很高的延迟, 所以为了不浪费CPU周期去等待, 最好在收到网络响应之前做些其他的事。 按顺序下载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import os import time import sys import requests COUNTRIES = 'CN IN US ID BR PK NG BD RU JP MX PH VN ET EG DE IR TR CD FR'....

created: 2023-04-04  |  updated: 2023-04-04  |  阿秀

《Fluent Python》第18章 使用asyncio包处理并发

前言 1 2 3 4 5 6 7 Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once. Not the same, but related. One is about structure, one is about execution. Concurrency provides a way to structure a solution to solve a problem that may (but not necessarily) be parallelizable. ———— Rob Pike (Co-inventor of the Go language) 一个使用线程的例子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import threading import itertools import time import sys LUCKY = 3 class Signal: go = True def spin(msg, signal): write = sys....

created: 2023-04-04  |  updated: 2023-04-04  |  阿秀

《Go语言设计与实现》01~02

调试源码 ./src/make.bash 脚本会编译 Go 语言的二进制、工具链以及标准库和命令并将源代码和编译好的二进制文件移动到对应的位置上 中间代码 go在编译的过程中会经过中间代码生成阶段,Go语言编译器的中间代码具有静态单赋值(Static Single Assignment、SSA)的特性 go build -gcflags -S main.go 汇编指令的优化过程(输出html): GOSSAFUNC=main go build main.go 编译原理 抽象语法树(Abstract Syntax Tree、AST) 静态单赋值(Static Single Assignment、SSA)是中间代码的特性,如果中间代码具有静态单赋值的特性,那么每个变量就只会被赋值一次。因为 SSA 的主要作用是对代码进行优化,所以它是编译器后端的一部分 指令集 x86指令集 arm指令集 Go语言编译器的源代码在src/cmd/compile中 编译器的前端一般承担着词法分析、语法分析、类型检查和中间代码生成几部分工作,而编译器后端主要负责目标代码的生成和优化,也就是将中间代码翻译成目标机器能够运行的二进制机器码。 Go 的编译器在逻辑上可以被分成四个阶段:词法与语法分析、类型检查和 AST 转换、通用 SSA 生成和最后的机器代码生成 词法分析 语法分析 类型检查阶段对make进行改写 Go 语言的很多关键字都依赖类型检查期间的展开和改写 源文件转换为抽象语法树,对整棵树的语法进行解析并进行类型检查之后,则认为树合法,转而将语法树转换为中间代码 Go 语言的编译器入口在 src/cmd/compile/internal/gc/main.go 文件中 会调用 cmd/compile/internal/gc.parseFiles 对输入的文件进行词法与语法分析得到对应的抽象语法树 得到抽象语法树后会分九个阶段对抽象语法树进行更新和编译,就像我们在上面介绍的,抽象语法树会经历类型检查、SSA 中间代码生成以及机器码生成三个阶段: 检查常量、类型和函数的类型; 处理变量的赋值; 对函数的主体进行类型检查; 决定如何捕获变量; 检查内联函数的类型; 进行逃逸分析; 将闭包的主体转换成引用的捕获变量; 编译顶层函数; 检查外部依赖的声明; 类型检查会遍历传入节点的全部子节点,这个过程会展开和重写 make 等关键字,在类型检查会改变语法树中的一些节点,不会生成新的变量或者语法树

created: 2023-04-04  |  updated: 2023-04-04  |  阿秀

《Go语言设计与实现》03-1 数组

我们可以看出 […]T{1, 2, 3} 和 [3]T{1, 2, 3} 在运行时是完全等价的,[…]T 这种初始化方式也只是 Go 语言为我们提供的一种语法糖,当我们不想计算数组中的元素个数时可以通过这种方法减少一些工作量 在不考虑逃逸分析的情况下,如果数组中元素的个数小于或者等于 4 个,那么所有的变量会直接在栈上初始化,如果数组元素大于 4 个,变量就会在静态存储区初始化然后拷贝到栈上 (这里的上下文是数组初始化) Go 语言中可以在编译期间的静态类型检查判断数组越界: 访问数组的索引是非整数时,报错 “non-integer array index %v”; 访问数组的索引是负数时,报错 “invalid array index %v (index must be non-negative)"; 访问数组的索引越界时,报错 “invalid array index %v (out of bounds for %d-element array)"; 数组和字符串的一些简单越界错误都会在编译期间发现,例如:直接使用整数或者常量访问数组;但是如果使用变量去访问数组或者字符串时,编译器就无法提前发现错误,我们需要 Go 语言运行时阻止不合法的访问: Go 语言运行时在发现数组、切片和字符串的越界操作会由运行时的 runtime.panicIndex 和 runtime.goPanicIndex 触发程序的运行时错误并导致崩溃退出

created: 2023-04-04  |  updated: 2023-04-04  |  阿秀

《Go语言设计与实现》03-2 切片

a[low : high : max] capacity will be set to max - low, low <= high <= max https://stackoverflow.com/questions/27938177/golang-slice-slicing-a-slice-with-sliceabc OSLICEHEADER 操作会创建我们在上面介绍过的结构体 reflect.SliceHeader,其中包含数组指针、切片长度和容量,它是切片在运行时的表示: type SliceHeader struct { Data uintptr Len int Cap int } 正是因为大多数对切片类型的操作并不需要直接操作原来的 runtime.slice 结构体,所以 reflect.SliceHeader 的引入能够减少切片初始化时的少量开销,该改动不仅能够减少 约0.2% 的 Go 语言包大小,还能够减少 92 个 runtime.panicIndex 的调用,占 Go 语言二进制的 约3.5%。 3.2.4 追加扩容 分为append后的切片覆盖原切片和不覆盖原切片两种 3.2.5 切片拷贝

created: 2023-04-04  |  updated: 2023-04-04  |  阿秀