数学建模社区-数学中国

标题: Python 迭代器、生成器 [打印本页]

作者: zhangtt123    时间: 2020-6-16 10:18
标题: Python 迭代器、生成器
一、可迭代对象" \" h( R' _; B! x; E9 j( V
字面意思:2 v) s, g3 B# H# [7 X
对象:Python 中一切皆为对象(巧了 Java 也是(手动滑稽))
( C; v; t0 O! f" h9 Y. D可迭代:可更新迭代,重复、循环的一个过程,每次更新迭代都会获得新的内容
7 f2 Q$ J6 m- H6 v专业角度:内部含有 '__iter__‘ 方法的对象6 p: p  _0 Y7 L' n
目前学过的可迭代对象:str、list、tuple、dict、set、range、文件句柄等) W( c& q+ a/ j; Y" }9 U% ^% |
判断一个对象是否是可迭代对象:看是否有 '__iter__' 方法,dir() 可以获取一个对象的所有方法;或者使用 isinstance(object, collections.iterable) 来判断对象是否是可迭代对象的一个实例4 z0 D5 l* p+ o2 C+ j7 p
1 b, N# V# v1 o! E$ S
! c. }% G3 J+ i# H0 @- t
优点:& Y5 b4 S  W' F& h* a7 ]( Q4 d
存储的数据直接能显示,比较直观:比如直接 print 一个可迭代对象,就会调用 __str__ 方法(相当于 Java 中的 toString),把可迭代对象的值打印出来
3 A9 _2 I' R5 B* w8 B/ U* o6 f拥有较多的方法,操作方便:增删查改等8 w6 q( j; R% Q8 J/ z8 N
缺点:4 F7 p6 d  N1 J8 ]
占用内存:一旦创建了一个可迭代对象,就会将该对象的内容全部加载到内存中7 X" m9 i% N; V- e0 b1 w2 z9 Y+ I
不能直接通过 for 循环,不能直接取值(通过索引、key等)。诸如通过 for i in iterable 这种形式获取元素实际上也是调用了 __iter__ 方法先将可迭代对象转换成迭代器再进行获取
9 \2 Z/ A% ]6 y6 V# A1 r4 j二、迭代器$ }& ~% I1 h5 Y% @' a2 [" l
字面意思:器,工具,迭代器也就是可以一直更新迭代取值的工具9 n- ?, o4 i: O5 s
专业角度:内部含有 __iter__ 方法且含有 __next__ 方法的对象就是迭代器;或者使用 isinstance(object, collections.iterator) 来判断对象是否是可迭代对象的一个实例
4 u! B# F* z# w6 s  g* ?% a( ^- g把一个可迭代对象转换成迭代器:使用 iter() 方法或使用对象的 __iter__ 方法
! o. Q! ^# q( m/ Q0 D% I5 O4 f* r8 z' q* F2 f1 A; U
迭代器取值:使用 next() 方法或对象的 __next__ 方法;当迭代器的值去玩了继续取,就会报StopIteration异常,所以一般使用迭代器需要做异常处理
' L0 S) \- k; R% Y( A9 c* x' o/ u* D& B: ]
优点% [  i/ @( h& [
节省内存:迭代器并不会一次性将对象的值全部加载到内存中,而是需要时才加载(类似 sed)
& Z$ G: y3 c# m: g( J惰性机制:next 一次只取一个值,绝对不多取
! D: h( ?0 j2 j% E. M' d$ o缺点:
" Y! s4 Q2 r. X2 s- w( E9 Z- c. i速度慢:需要一直 next
( |/ w* E( S) ]# d7 d, X/ Y不能回头:只能一直往下取值,取过的值没保存就没了`
# s  _( r& r. a- Q* H不能直观的看到里面的数据! ^. J' u7 S/ f; U. Y4 L
三、可迭代对象与迭代器对比
( g2 a# e( U0 z/ l0 [可迭代对象:3 n5 k+ W- V/ {; j& x$ }# q
私有方法多,操作灵活(比如列表,字典的增删改查,字符串的常用操作方法等)
, n6 ~% i8 }! ^0 ^# P* W. c3 h直观,可以直接看到里面的数据
& `( q# j8 i3 K, z0 Q( G$ K! J( a占用内存
& Q7 |- `- K2 d. I" Z% s" s& F不能直接通过循环迭代取值
9 O$ c% `3 V/ G4 y  ~应用:当你侧重于对于数据可以灵活处理,并且内存空间足够,将数据集设置为可迭代对象是明确的选择3 w3 t, q7 x& T" ~$ T5 H
迭代器:* Z$ ~$ l; m4 D& \
节省内存,按需取值7 g! x2 \2 h) {+ I9 C) x2 S
可以直接通过循环迭代取值6 W5 {! D. _9 C
数据不直观,操作方法单一
9 c; |" K) w% w! B- r- }4 ?应用:当你的数据量过大,大到足以撑爆你的内存或者你以节省内存为首选因素时,将数据集设置为迭代器是一个不错的选择* g# K5 W7 H; g+ N% v. j
四、生成器
9 w* h# P1 y3 u! B. v生成器的本质就是迭代器,唯一的区别是生成器是我们自己用代码构建的数据结构,迭代器是 Python 提供的,或者通过可迭代对象转化得来的0 c' |% |' }5 y# p! ?% x- i2 w

& o2 r/ b+ F" V定义生成器的方式:$ }7 A5 p7 c0 N$ [
通过生成器函数构建生成器  M$ m, J/ r# Y& |. {: b% f& Y
111.png 0 N  |% Z. G1 B  o) E( K
! g4 {* ~0 q3 p$ A
6 f' q4 N1 ~& E' d
这就是最简单的生成器函数。实际上这个 yield 就替代了 return,不仅将函数变成了生成器函数,还会将后面的值在调用 __next__ 的时候返回出来
/ C& P, t) }; t1 ?
3 n3 x2 D9 F2 H4 y也可以在一个函数里定义多个 yield/ ?, u9 q! e( C6 B! L

- ]0 ]$ ?5 P' ?2 G之前说过,生成器本质上还是迭代器,一个 yield 对应一个 next,当 next 的数量超过了 yield,就会报 StopIteration9 C2 l  Y# n3 o6 b/ ]. ~
' I! y; [# D; L9 k/ R
yield 与 return 的区别  U" @3 p- J/ d' J2 x$ t1 H5 K

) v" L- B# h* g5 oreturn一般在函数中只设置一个,他的作用是终止函数,并且给函数的执行者返回值' r  b5 _1 h) R. F5 d) r& |
yield在生成器函数中可设置多个,他并不会终止函数,next会获取对应yield生成的元素
0 Q& {& E$ {' r5 `2 Y2 O& n, c4 r应用举例:
# b' C) `7 {5 ?) g, T
6 b5 f- o& x7 V6 F# u. \买 5000 个包子,假设这个老板很厉害,一下子就把 5000 个包子做出来卖给我们,可是我们只有 5 个人,一下子吃不完,那包子就会冷掉、臭掉、被丢掉浪费了+ {8 |: u) t1 G# T2 E2 X
222.png
- O, W- t% N; i! U! Q; z
4 l5 a/ m% M9 t. y如果这个老板可以在我们需要多少个包子就做出来多少个包子的话,这样做出来的包子就不会被浪费了(比如我们每个人一口气能吃 40 个包子,那每次就做 200 个包子):
  R7 D  N3 N! k/ p9 I! y 3333.png 7 A8 U5 ^  G, w9 {$ U* f% f+ R
6 n+ F" [# s/ U, b+ f, z% K) c3 a5 a
除了 使用 next() 触发 yield 之外,生成器还有一种方法 send(),这个方法可以在调用 yield 的同时传值给生成器内部+ U" U; {: I' e* D( m; A9 D$ u

; g5 n" {5 ?- k) Q& z! E  Y1 D7 C可以看到在使用 next() 的时候,只能获取到 yield 的值,但不能传递值+ s8 P7 m1 v: |+ ^  D) m
; i3 w2 ?8 A3 h: a  E" k) p4 {
在使用 send() 的时候,可以将参数传入生成器中使用& Y" _1 B# \9 \+ j3 O" ~  Q7 d+ l
0 {: O, A, b! ~! {: j# {7 T) F
需要注意的是第一次不能直接调用 send() 传参,因为每次调用生成器的时候,实际上只会返回 yield 后面的内容,然后生成器就停止了(睡眠了?),而 send() 传入的参数要通过 yield 传入生成器中(每次调用生成器在 yield 停止,然后在 yield 恢复继续允许),第一次调用并没有 yield 给我们传入参数,可以使用 send(None),可以打断点自己分析一下
, F1 J4 \$ m* k7 A1 G/ ?7 ^4 y( L5 @2 E# ]
yield 会将它后面跟着的对象直接返回,如果它后面跟着的是可迭代对象,也可以使用 yield from 将这个可迭代对象变成迭代器返回
  ~. A- d' @: |/ e. Y2 L% A
5 R+ F2 R5 p0 {! u6 O5 g- z
1 |; O5 s  C, Qyield from 是将列表中的每一个元素返回,所以写两个 yield from 并不会有交替执行的效果
& {0 A, n/ @# l# k- _
6 b9 G  j/ ]; @& s
+ v1 d$ x. v$ D5 C& Z, @8 J- s, E( w" \* |
通过推导式构建生成器& c  O" s! U9 d7 J- \  q
列表推导式:
% a3 I- i  i4 Y9 p2 b1 q8 N
8 T9 b/ W; A% S' Q4 c生成器表达式:和列表推导式差不多,把 [] 改成 () 即可
- r9 `2 C1 z  U, ^3 Z' j9 k3 f7 @0 V# S- b! R

' e% r; u1 j7 d/ T( Y/ {9 ]8 C3 P. J列表推导式和生成器推导式的区别:
5 V% Z, O" [9 a' s) E, X
/ ~- z$ }: d  {1 j8 p列表推导式比较耗内存,所有数据一次性加载到内存;而生成器表达式遵循迭代器协议,逐个产生元素" r+ m- Y0 L9 c& d; c
得到的值不一样:列表推导式得到的是一个列表;生成器表达式获取的是一个生成器
! ^9 k7 W# y2 P$ K3 S# q列表推导式一目了然,生成器表达式只是一个内存地址
) N( k$ U( ]  f; E% K————————————————0 C- V1 a: g# d2 U
版权声明:本文为CSDN博主「阿玮d博客」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
/ S. R: U" D. E" i9 G% A原文链接:https://blog.csdn.net/weixin_42511320/article/details/105676143
1 i2 P* U3 b' P' P7 J# \
& _. X& O& z8 i: C+ K+ M0 I
& T0 q3 e3 D2 _; X% i1 i% b
作者: dwadasd    时间: 2020-6-24 20:40
多谢大神分享
% j; i9 Y4 `8 K& a% G4 u# _; j3 A
作者: dwadasd    时间: 2020-6-24 20:41
太好了太好了& L  z, Y: b5 H* G- n9 L0 a+ C, }! b





欢迎光临 数学建模社区-数学中国 (http://www.madio.net/) Powered by Discuz! X2.5