一、推导式
(一).列表推导式(集合推导式也同理于此)
(1).例1:基本的列表推导式
(2).例2:利用列表推导式,取出1-20内所有偶数
li = [i for i in range(1, 21) if i % 2 == 0] # 如果只有一个条件,要把if语句写在最后面 # 第一个i是放入列表的值,后面都是推导的公式
print(li) # 第一个 i 相当于下面等价方案中的 append(i)。把 i 换成 "a" ,输出的列表中全是 "a"
# 上面的列表推导式等价于: for i in range(1,21): if i%2==0: li.append(li) print(li)
(3).例3:利用列表推导式,取出1-20内所有数,其中奇数用字符"a"代替,偶数则正常输出
li = [i if i % 2 == 0 else "a" for i in range(1, 21)] # 添加多个条件的时候,要写在前面 print(li)
(4).例4:列表推导式练习:将列表li=['a','b','c','d','e']倒序
# print([li.pop() for i in range(len(li))]) # pop() -> remove and return the last element
(二).字典推导式
(1).例1:
a = {str(i): i for i in range(5)} print(a) # {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4}
(2).例2:
a = {"i": i for i in range(5)} print(a) # {'i': 4}
注意:因为字典的键是唯一的,此例的字典推导式中,把键写死了,每一次推导其实都是在修改原先的值。
(3).例3:
print({i: 'a' for i in range(5)}) # {0: 'a', 1: 'a', 2: 'a', 3: 'a', 4: 'a'}
此例中是把值写死了,相当于dict.fromkeys([0,1,2,3,4],"a")
(4).例4:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
bag = [2, 3, 1, 2, 5, 6, 7, 9, 2, 7] print({i: bag.count(i) for i in bag}) # {2: 3, 3: 1, 1: 1, 5: 1, 6: 1, 7: 2, 9: 1} # 开销很大,因为每次count()时,都会对列表进行遍历
更佳的方式,是采用已有的库:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
from collections import Counter bag = [2, 3, 1, 2, 5, 6, 7, 9, 2, 7] count = Counter(bag) print(type(count)) # <class 'collections.Counter'> print(dict(count)) # {2: 3, 3: 1, 1: 1, 5: 1, 6: 1, 7: 2, 9: 1}
count是一个类似字典的对象
(5).例5:
(三).小括号包裹的话,是返回一个生成器对象:
print((i for i in range(5))) # <generator object <genexpr> at 0x01760960>
可以用tuple()或者list()来查看:
print(tuple(i for i in range(5))) # (0, 1, 2, 3, 4)
(四).小技巧
(1).对推导式中的元素进行运算操作
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
""" 你有一个list: bag = [1, 2, 3, 4, 5] 现在你想让所有元素翻倍,让它看起来是这个样子:[2, 4, 6, 8, 10] """ print([i*2 for i in bag]) # [2, 4, 6, 8, 10]
二、迭代器(iterator)
迭代器它也是一个可被迭代的对象,同时它是一个返回数据的对象,但一次只返回一个元素,拥有着一边循环一边计算的机制。
一个迭代器,它必须同时实现__iter__(iterable)和__next__(iterator)这两个魔法方法,这是迭代器的协议。
__iter__()是内建函数iter(iterable)的隐式调用,将一个可迭代对象构建成一个迭代器。
__next__()是内建函数next(iterator)的隐式调用,实现迭代器,本质是去一个迭代器中进行"取值"操作,取出迭代器的下一个元素。
(一).构建一个迭代器
(1).使用内建函数iter()
内建函数iter()会为自动地将一个可迭代对象构建成一个迭代器,python会自动去调用__iter__(),并同时实现__next__()
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
# define a list my_list = [4, 7, 0, 3] # get an iterator using iter() my_iter = iter(my_list) # iterate through it using next() # prints 4 print(next(my_iter)) # prints 7 print(next(my_iter)) # next(obj) is same as obj.__next__() # prints 0 print(my_iter.__next__()) # prints 3 print(my_iter.__next__()) # This will raise error, no items left next(my_iter) """ Traceback (most recent call last): File "D:/python_local/test2.py", line 28, in <module> next(my_iter) StopIteration """
迭代器对象必须要有一个变量去接收它,next(iterator)隐式去调用__next__(),__next__()是类中的魔法方法,没有给next()指定迭代器对象,去哪个对象里next元素?
(2).自定义构建一个迭代器
一个迭代器必须同时实现__iter__()和__next__():
__iter__()返回迭代器对象本身,把一个可迭代的对象变成迭代器。如果需要,可以执行一些初始化操作。
__next__()返回序列中的下一个元素。当序列穷尽时,如果再继续next(),那么就会抛出StopIteration异常。通俗理解,__next__就是在迭代器中进行"取值"。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
class MyIter: def __iter__(self): self.num = 1 return self def __next__(self): num = self.num self.num += 2 return num mi = MyIter() mi.__iter__() # 必须显示调用,不然无法变成一个迭代器 while 1: print(next(mi)) """ 1 3 5 7 ... 直到因撑爆内存后,死机了才会结束 """
(3).去迭代器进行"取值""
可以使用next()手动一个个去取,也可以使用for循环对迭代器对象进行遍历。
(二).查看一个对象是否为迭代器
迭代器对象必须同时拥有__iter__和__next__
(1).使用dir()函数
my_list只出现了__iter__,而没有__next__,那它只可一个可迭代对象,并不是一个迭代器对象。
之后的iter(my_list)操作,python会隐式地去将my_list构建成为一个迭代器对象。
my_iter同时出现了__iter__和__next__,那么它就是一个迭代器对象。
# 枚举就是个迭代器 g = enumerate([1, 2, 3]) print(dir(g)) # dir(g)可以看到有__iter__和__next__
(三).迭代器只能往前,不能后退
例:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
li = [1, 2, 3] g = iter(li) print(next(g)) # 打印出1,把第一个元素取出了 print("---") for i in g: # 这里只会取出后面的2个值 print(i) # 2 3
此例中,当取出迭代器中的第一个元素后,第一个元素就不存在了,"定位"定到了第二个元素。当使用for循环遍历时,因为第一个元素已经不存在了,所以就会从"定位"的第二个元素开始取值,然后全部取完。
(四).迭代器穷尽,序列中的元素将不复存在
先看下图中的案例:
数据来源于(二).(1).的my_iter,之前没有对my_iter进行任何的取值操作。
当for循环遍历完迭代器之后,迭代器中的序列就不复存在了,也就是在内存中被销毁了,只留下了my_iter这个对象,但对象中已经没有任何元素。
所以,当再次去for循环遍历迭代器的时候,就取不到任何元素了。序列中的元素已经没了,那么next()取不到元素,就会抛出StopIteration异常。
这也是迭代器的协议之一:一次性消费,不可以被循环遍历两次。(也就是过了这村儿没这店儿)
(五).迭代器不同于可迭代对象
迭代器和可迭代对象都是可被遍历的对象,但两者有着本质的区别。
(1).首先,迭代器比可迭代对象多了一个专属的魔法方法:__next__()。可迭代对象中,只有__iter__(),而没有__next__()
(2).迭代器的特性是惰性计算,创建迭代器的时候不会马上就生成所有的元素,只在对迭代器进行实质性地操作时,才会生成元素,而且是边生成边返回,一次只返回一个元素。
而可迭代对象,在创建的时候就将所有的已有元素,都放进内存中去了。
(3).迭代器使用完毕后,序列中的元素就不复存在,想要再从头取值,必须进行手动重新创建。而可迭代对象,只要这个对象不被销毁,就可以一直使用。
(4).迭代器只能往前不能后退,而可迭代对象可以随意操作前进后退。
(5).迭代器不可以被循环遍历两次、不能访问其长度,也不能使用索引。
(六).示例
(1).例1:
首先迭代器是一个可以被迭代的对象,所以符合zip的规则。同时,此例很好地证明了,迭代器是逐个计算返回的特性。
第一次组合的时候,先返回1,再返回2,于是组成了(1,2);第二次组合的时候,先返回3,再返回4,于是组成了(3,4)
(七).参考文献
https://www.programiz.com/python-programming/iterator
https://zhuanlan.zhihu.com/p/34157478
https://zhuanlan.zhihu.com/p/39640305
三、生成器(generator)
(一).生成器的本质
生成器的本质就是迭代器,生成器它有两种表现形式:(1).用小括号包裹起来的推导式;(2).函数中出现了yield这个关键字。
生成器与迭代器一样,有同样的两种取值方式:next()或for循环遍历。
(二).yield关键词
当函数中出现了yield关键词时,next()或for循环遍历操作生成器的时候,每次遇到yield时,函数会暂停并保存当前所有的运行信息,然后返回紧跟在yield后面的值。当下一次执行next()时,就从当前位置继续运行。
(三).生成器和迭代器的共同点
生成器和迭代器最大的优点就是节省内存开销,因为生成器同样具有惰性计算的特性,只在需要计算的时候才计算。而且计算完就释放,无法再次去循环遍历。
(四).生成器和迭代器的区别
生成器的本质就是迭代器,硬要说区别,我个人的理解就是两者的表现形式不一样。生成器只有两种表现形式,而迭代器的表现形式则是多样的,enumerate、zip、reversed和其他一些内置函数会返回迭代器
(五).示例
如下演示一个简单的生成器:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
def fun(): i = 1 while i < 5: print("before yield", i) # yield # 实现生成器的功能。1、暂停,2、返回值。 yield "yield message:pause" # 将会返回一个信息 i += 1 print("after yield", i, end="\n") # 函数体,因为并没有去调用这个函数 print(fun) # <function fun at 0x0032E810> # 函数中有yield这个关键词,那么这个函数就是生成器了 print(fun()) # <generator object fun at 0x002900B0> print("-----------------") print(next(fun())) print(next(fun())) print(next(fun())) """ 打印结果: before yield 1 yield message:stop before yield 1 yield message:stop before yield 1 yield message:stop 相当于每一次都重新创建了生成器,每一次都在操作新的生成器,所以结果一直都是1 """ print("-----------------") for i in fun(): print(i) """ before yield 1 yield message:stop after yield 2 before yield 2 yield message:stop after yield 3 before yield 3 yield message:stop after yield 4 before yield 4 yield message:stop after yield 5 yield会暂停函数的运行并返回紧跟在后面的值,相当于打了一个断点,停在了断点处。 当下一次next()或者是遍历的时候,会从断点处恢复,并往下执行。 """
(六).陷阱之一:部分消耗生成器
两次询问9是否存在于同一个生成器中,得到了不同的答案。
这是因为,第一次询问时,Python已经对这个生成器进行了遍历,也就是调用next()函数去逐个逐个地查找9,找到后就会返回True。当第二次再询问9是否存在时,会从上次的位置继续next()查找。
(七).参考文献
https://www.programiz.com/python-programming/generator
https://zhuanlan.zhihu.com/p/39640305
五、模块和包
(一).模块:本质上就是py文件。分为:内置模块,第三方模块。
(1).内置模块的使用:
导入所有内容:import modulename;很直观,但很占内存。
指定导入:from modulename import funcationname;明确知道自己要用什么方法。
(2).自定义模块的使用:
与当前py文件是同级路径:直接导入。
不同路径导入的参考:
import sys # 别忘了先导入这个 sys.path # 路径先调出来。返回一个列表,是python的安装目录 sys.path.append(r"") # 小括号内可添加自己py文件的绝对路径,记得取消转义 # 再 import modulename 就可以了
(3).得在自己写的py文件的最后一行加入:
if __name__ == '__main__': functionname1() functionname2()
# 有这段代码。测试是本身就有,还是导入进来的。
# 一定要对这个py文件本身执行,运行了,才会有结果。
(二).包:很多py文件放在一个文件夹中。
(三).if __name__ == '__main__':
就是个if判断,'__main__'就是个字符串,判断是导入的还是直接执行的。
当import一个py模块(文件)的时候,会把那个py模块(文件)执行一遍。
例1:
我有一个"test1.py"的模块(文件),如下代码:
import test2 print(__name__) # __main__ print(test2.__name__) # test2 """ 运行结果: zyb111 __main__ test2 """
有另一个"test2.py"的文件,如下代码:
print("zyb111") if __name__ == '__main__': print("zyb222")
"test1.py"中,import了"test2",那么"test2.py"就被执行了一遍。所以在"test1.py"的运行结果中,会出现zyb111,因为 import test2 的时候,"test2.py"被执行了一遍。
为什么打印不出zyb222?
"test2.py"是被引入进"test1.py"中的。"test2.py"中就有了if判断,判断的结果:它们两个不是同一个name。
看"test1.py"文件中的这条代码 print(test2.__name__),这条代码特意显示了一下"test2.py"是什么名字。返回的结果是 test2,但现在执行的是"test1.py"这个文件呀!"test1"=="test2"吗?显然是False,那就不会有zyb222了。
(四).相对路径导入
(五).限制外部import
当发布python第三方包时, 有时候并不是希望代码中所有的函数或者类可以被外部import。
在__init__.py中添加一个__all__列表,该列表中填写可以import的类或者函数名,可以起到限制import的作用,防止外部import其他函数或者类。
例如: