“Python有什么好学的”这句话可不是反问句,而是问句哦。shell
主要是煎鱼以为太多的人以为Python的语法较为简单,写出来的代码只要符合逻辑,不须要太多的学习便可,便可从一门其余语言跳来用Python写(固然这样是好事,谁都但愿入门简单)。缓存
因而我便记录一下,若是要学Python的话,到底有什么好学的。记录一下Python有什么值得学的,对比其余语言有什么特别的地方,有什么样的代码写出来更Pythonic。一路回味,一路学习。app
谈到生成器/迭代器,人们老是喜欢用斐波那契数列来举例。函数
斐波那契数列,数学表示为a(1)=0, a(2)=1, a(i)=a(i-1)+a(i-2) (i>=3): 0 1 1 2 3 5 8 13 21 ... 用一句话说,就是第三位数起,当前这位数是前两位的和。
固然,使用斐波那契来举例是一个很合适的选择。学习
那么,为何说到生成器/迭代器就喜欢斐波那契,而不是其余呢?大数据
斐波那契数列有一个特征:当前这位数的值,能够经过前两位数的值推导出来。好比,我知道了第n位数是5,第n+1位数是8,那我就能够轻易地得出,第n+2位必然是5+8=13。spa
即,斐波那契数是能够经过推导式得出的。code
就是这样一种相似于冥冥注定的感受:当前的平行空间,是由你以前的选择决定的。并且是钦定好的,你接下来的每一步,其实都已经被决定了,由什么决定呢,由你之前走过的路决定的。对象
那么,换句话来讲,即能由推导式得出的数列,其实均可以用来作生成器/迭代器的例子。例如,煎鱼用一条式子y(n)=y(n-1)^2 + 1 (n>=1), y(1)=1
,同样能拿来当例子。内存
既然这样,那就用y(n)=y(n-1)^2 + 1 (n>=1), y(1)=1
吧。
一开始,人们的思想很简单,若是要求y(n)=y(n-1)^2 + 1 (n>=1), y(1)=1
中y数列的第13位,即y(13),那很简单,就轮询到13个就行了。
def y(): y_current = 1 n = 1 while n < 13: y_current = y_current ** 2 + 1 n += 1 print(n, y_current)
输出也挺长的,就截个图算了:
这个时候,这个代码是彻底够用的。接下来,煎鱼加点需求(PM般的狞笑):
接下来,函数改为:
def y(n_max): y_current = 0 n = 0 ret_list = [] while n < n_max: y_current = y_current ** 2 + 1 n += 1 ret_list.append(y_current) return ret_list if __name__ == '__main__': for i in y(13): print(i)
看起来没什么毛病,彻底符合要求。可是,问题出如今当函数的参数n_max较大的时候。要多大呢,煎鱼尝试输出n_max=13000000,即:
if __name__ == '__main__': for i in y(13000000): print(i)
咱们看得出来,这个函数的计算量十分大,以煎鱼当前的电脑配置(Macbook pro 2017 i5 8G RAM),等了一两分钟还没结束,只好强行中断了。
程序为何卡那么久呢。由于在函数的逻辑中,程序试图将13000000个值都计算出来再返回list以供外接轮询,并且这13000000个一个比一个难算(愈来愈大,指数增加)。同时,该list也占了庞大的内存空间。
到了这个时候,煎鱼终于要引入生成器了。
其实煎鱼就加入了一个yield,并稍做修改:
def y_with_yield(n_max): y_current = 0 n = 0 while n < n_max: y_current = y_current ** 2 + 1 n += 1 yield y_current if __name__ == '__main__': for i in y_with_yield(13000000): print(i)
虽然屏幕滚动得很慢,可是起码是在实时地滚动的。
加入了yield变成这样,其实就是搞成了一个简单的生成器。在这里,生成器的做用有:
for i in y_with_yield(13000000)
的循环中,每一次循环程序才会进入函数去计算,而不会把所有结果都计算出来再返回暂时给出初步结论:
咱们再来看下生成器的其余用途吧。
在读文件或处理文件时使用缓存是颇有必要的,由于咱们老是不知道文件会有多大,文件的大小会不会把程序给拖垮。
煎鱼新建一个文件(假设叫test.txt),并往其中写入文本,运行如下代码:
def read_file(file_path): BLOCK_SIZE = 100 with open(file_path, 'rb') as f: while True: block = f.read(BLOCK_SIZE) if block: yield block else: return if __name__ == '__main__': for i in read_file('./test.txt'): print(i) print('--------------block-split--------------')
咱们把100个长度分为一个block,这个block就至关于咱们的缓存:先从文件中读100个,而后让程序处理这100个字符(此到处理为print),再读下一个100。其中block-split的输出是为了让咱们更好地辩识出block的头尾。
经过yield瞎搞出来的简易生成器有一个很大的限制,就是必需要在循环内。
虽然“迭代”和“循环”有关联,可是当生成器的生成逻辑无比复杂时,好比“推导”的方法已经没法用数学推导式表达时,或者某种场景下的业务逻辑比较复杂以致于没法直接经过循环表达时,生成器类来了。
生成器类看起来很简单,其实就是将煎鱼在上面写的简单生成器写成一个类。
重点就是,咱们得找到“推导”,推导在这里是指next函数 —— 咱们实现的生成器类最重要的就是next()。
咱们来实现上面的y函数的生成器类:
class Y(object): def __init__(self, n_max): self.n_max = n_max self.n = 0 self.y = 0 def __iter__(self): return self def next(self): if self.n < self.n_max: self.y = self.y ** 2 + 1 self.n += 1 return self.y raise StopIteration() if __name__ == '__main__': y = Y(13) for i in y: print(i)
有几点是须要注意的:
__iter__()
函数,而返回的不必定是self,可是须要生成器接下来,煎鱼带来一段很无聊的表演,来表示__iter__()
函数而返回的不必定是self:
class SuperY(object): def __init__(self, n_max): self.n_max = n_max def __iter__(self): return Y(self.n_max) if __name__ == '__main__': sy = SuperY(13) for i in sy: print(i)
这段代码的输出和上一段如出一辙。
这里照妖镜的意思,指一个能鉴别某对象(甚至不是对象)是否一个生成器的东西。
提及来,可能会有点多余并且零碎。
其中有三个函数:
咱们把前面写过的y(带yield的函数),和Y(生成器类)导入后,进行实验观察:
from use_yield import y_with_yield as y from iter_obj import Y from inspect import isgeneratorfunction, isgenerator from types import GeneratorType from collections import Iterator if __name__ == '__main__': print(isgeneratorfunction(y)) # True print(isgeneratorfunction(Y)) # False print(isgeneratorfunction(y(5))) # False print(isgeneratorfunction(Y(5))) # False print(isgenerator(y)) # False print(isgenerator(Y)) # False print(isgenerator(y(5))) # True print(isgenerator(Y(5))) # False print("") print(isinstance(y, GeneratorType)) # False print(isinstance(y(5), GeneratorType)) # True print(isinstance(Y, GeneratorType)) # False print(isinstance(Y(5), GeneratorType)) # False print("") print(isinstance(y, Iterator)) # False print(isinstance(y(5), Iterator)) # True print(isinstance(Y, Iterator)) # False print(isinstance(Y(5), Iterator)) # True
实验的结论为:
Python里面,range和xrange有什么不一样,用哪一个更好,为何?
对的,就是和生成器有关系,嘿嘿。
先这样吧
如有错误之处请指出,更多地请关注造壳。