7.15迭代器、生成器及常用内置方法

迭代器

1.什么是迭代器

  迭代:更新换代(重复)的过程,每次的迭代都必须基于上一次的结果

n = 0
while True:
    print(n)
# 不算迭代,因为只是简单的重复


s = 'hello'
n = 0
while n < len(s):
    print(s[n])
    n += 1
# 重复 + 每次迭代都是基于上一次的结果而来的才是迭代

  迭代器:迭代取值的工具

2.为什么要用

  迭代器给我们提供了一种不依赖于索引取值的方法

3.可迭代对象

n = 1
f = 1.1
s = 'hello'
l = [1,2,3,4]
t = (1,2,3,4)
s1 = {1,2,3,4}
d = {'name':'francis'}
f1 = open('xxx.txt','w',encoding='utf-8')
res = s.__iter__()  # 相当于res = iter(s)
res1 = l.__iter__()  # res1 = iter(l)
res2 = t.__iter__()  # res2 = iter(t)
res3 = s1.__iter__()  # res3 = iter(s1)
res4 = d.__iter__()  # res4 = iter(d)
res5 = f1.__iter__()  # res5 = iter(f1)
print(res,res1,res2,res3,res4,res5)
print(f1)
View Code

(1)只有内置方法中有 __iter__ 方法的都叫做可迭代对象

(2)可迭代对象执行内置的 __iter__ 方法得到就是该对象的迭代器对象

(3)文件对象执行内置的 __iter__ 之后还是本身,没有任何变化,文件对象本身就是迭代器对象

  所以基本数据类型中是可迭代对象的有:字符串(str)、列表(list)、元组(tuple)、字典(dict)、集合(set)、文件对象

补充:

  针对双下线开头双下划线结尾的方法(__iter__),推荐读:双下+方法名

迭代器取值

1.迭代器对象及迭代器取值(__next__)

l = [1,2,3,4]
iter_l = l.__iter__()  # 把可迭代对象l转成迭代器对象
print(iter_l.__next__())  # 迭代器取值,调用__next__,而且迭代器对象的取值必须用__next__
print(iter_l.__next__())  # 一次取一个
print(iter_l.__next__())
print(iter_l.__next__())
print(iter_l.__next__())  # 如果取完了,直接报错,报错StopIteration

  (1)内置方法中既有 __iter__ 方法,也有 __next__ 方法的就是迭代器对象

  (2)只有 __iter__ 方法的是可迭代对象,迭代器对象一定是可迭代对象,而可迭代对象不一定是迭代器对象

  (3)迭代器取值的特点:只能往后依次取,不能回退

"""
问:__iter__方法就是用来帮我们生成迭代器对象的
而文件对象本身就是迭代器对象,为什么还内置中还有__iter__方法?
"""
f1 = open('xxx.txt','r',encoding='utf-8')  # 本来就是迭代器对象
iter_f = f1.__iter__()
print(iter_f is f1)  # 比较内存地址是否相同

l = [1,2,3,4]
iter_l = l.__iter__()  # 生成迭代器对象
iter_2 = iter_l.__iter__().__iter__().__iter__()
print(iter_2 is iter_l)  # 比较内存地址是否相同

  结论:迭代器对象无论执行多少次 __iter__ 方法得到的还是迭代器对象本身

2.异常处理

迭代器取完值了会报错

l = [1,2,3,4]
iter_l = l.__iter__()  # 把可迭代对象l转成迭代器对象
print(iter_l.__next__())  # 迭代器取值,调用__next__,而且迭代器对象的取值必须用__next__
print(iter_l.__next__())  # 一次取一个
print(iter_l.__next__())
print(iter_l.__next__())
print(iter_l.__next__())  # 如果取完了,直接报错,报错StopIteration

如何让它不报错,使用try + except

l = [1,2,3,4]
iter_l = l.__iter__()  # 把可迭代对象l转成迭代器对象
while True:
    try:  # 标识下面的代码可能会出错
        print(iter_l.__next__())
    except StopIteration:  # 如果出现StopIteration这个错误,就走下面这一步
        print('迭代器取完值了')
        break

for循环的本质

d = {'name':'francis','password':'123','hobby':'read'}
for i in d:  # for循环后面的in跟的必须是一个可迭代对象
    print(i)

1.for循环内部的本质

  (1)将in后面的对象调用 __iter__ 转换成迭代器对象

  (2)调用 __next__ 对这个迭代器迭代取值

  (3)内部有异常捕获StopIteration,当 __next__ 报了这个错,就自动结束循环

d = {'name':'francis','password':'123','hobby':'read'}
iter_d = d.__iter__()  # 将in后面的对象调用 __iter__ 转换成迭代器对象
while True:
    try:  # 标识下面的代码可能会出错
        print(iter_d.__next__())  # 调用 __next__ 对这个迭代器迭代取值
    except StopIteration:  # 如果出现StopIteration这个错误,就走下面这一步
        print('迭代器取完值了')
        break  # 结束循环

2.迭代取值的优缺点

优点:

  (1)不依赖于索引取值

  (2)内存中永远只占一份空间,不会导致内存溢出(一次只取一个值)

缺点:

  (1)不能够获取指定的元素

  (2)取完元素之后会报StopIteration这个错

生成器

1.本质

  用户自定义的迭代器,本质就是迭代器

2.如何自定义迭代器

  自定义函数 + yield的方法

def func():
    print('第一个值')
    yield 111  # yield后面跟的值就是调用迭代器__next__方法时你能得到的值
    print('第二个值')
    yield 222
    print('第三个值')
    yield 333
    print('第四个值')
    yield 444,555,666  # yield既可以返回一个值也可以返回多个值,并且多个值也是按照元组的形式返回
g = func()  # 生成器初始化:函数内如果有yield关键字,那么加括号执行函数的时候并不会触发函数体代码的运行,而是将函数变成迭代器
print(g)
print(g.__next__())  # 代码运行到第一个yield后就暂停
print(g.__next__())  # 直到再次取值,一次取一个值
print(g.__next__())
print(g.__next__())
print(g.__next__())  # 取完值就报错

3.如何自定义一个函数,实现内置函数range的功能

for i in range(1,10,2):
    print(i)

  range其实就是一个可迭代对象

实现:

def my_range(start,end,step=1):
    while start < end:
        yield start  # 第一个值小于第二个值就返回第一个值
        start += step  # 然后第一个值加上步长再判断

for i in my_range(1,100,2):  # 步长可通过传参修改
    print(i)

yield表达式形式(了解即可)

def people(name):
    print('%s 准备开吃'%name)
    while True:
        food = yield  # 代码运行到第一个yield后就暂停
        print('%s 吃了 %s'%(name,food))
g = people('francis')  # 当函数内有yield关键字的时候,调用该函数不会执行函数体代码,而是将函数变成生成器
g.__next__()  # 必须先将代码运行至yield,才能够为其传值
g.send('蔬菜')  # 给yield左边的变量传参,并且触发__next__方法
g.send('')

1.yield

  (1)帮你提供了一种自定义生成器的方式

  (2)会帮你将函数的运行状态暂停住

  (3)可以返回值

2.yield与return之间的异同点

相同点:

  都可以返回值,并且都可以返回多个

不同点:

  (1)yield可以返回多次值,而return只能返回一次值,而且函数立即结束

  (2)yield还可以接受外部传入的值(send)

生成器表达式

  跟列表、字典、集合生成式一样,不同的是外面用括号

res = (i for i in range(1,10) if i != 4)  # 生成器表达式
print(res)
print(res.__next__())
print(res.__next__())
print(res.__next__())
print(res.__next__())

1.如何统计文件中的字符个数

(1)第一种

f = open('xxx.txt','r',encoding='utf-8')
data = f.read()  # 把文件内容一次性读完
print(len(data))  # 统计字符个数
f.close()

  该方法占内存:如果文件过大,一次性读完太占内存

(2)第二种:for循环

with open('xxx.txt','r',encoding='utf-8') as f:
    n = 0
    for line in f:  # 一次只读取文件的一行内容
        n += len(line)  # 统计每一行的字符数并依次相加
    print(n)

  该方法节省内存:内存中每次只会有一行内容

(3)第三种:生成器

g = (len(line) for line in f)  # 生成器表达式
print(sum(g))  # 求和(内部for循环)

  把生成器g中的值for循环出来然后相加

2.面试题

def add(n,i):
    return n+i
def test():
    for i in range(4):
        yield i
g=test()
for n in [1,10]:
    g=(add(n,i) for i in g)
res=list(g)

# A. res=[10,11,12,13]
# B. res=[11,12,13,14]
# C. res=[20,21,22,23]
# D. res=[21,22,23,24]
# 选出正确答案

解答:

def add(n,i):
    return n+i
def test():
    for i in range(4):
        yield i
g=test()  # 不执行函数体代码,而是将函数变成生成器,所以不会执行函数里的for循环
for n in [1,10]:  # for循环两次,第一次n=1,第二次n=10
    g=(add(n,i) for i in g)
    '''
    第一次for循环时第二个g为生成器test(),也就是g1=(add(n,i) for i in test())
    第二次for循环时第二个g为上次循环得到的生成器g1,也就是g2=(add(n,i) for i in (add(n,i) for i in test()))
    两次循环结束,得到的是两个生成器,都没有执行生成器中的for循环
    '''
res=list(g)
'''
其实求的就是生成器g2的里面的值,此时求值时才会执行各生成器中的for循环,g2时n=10
把n=10代入g2生成器中也就是:
g2 = (add(10,i) for i in (add(10,i) for i in range(4)))
res = list(g2)
print(res)
答案是C
'''
# A. res=[10,11,12,13]
# B. res=[11,12,13,14]
# C. res=[20,21,22,23]  正确答案
# D. res=[21,22,23,24]
View Code

常用内置方法

1.abs:求绝对值

print(abs(-11.11))  # 求绝对值

2.all、any:

l = [0,1,2]
print(all(l))  # 只要有一个元素为False就返回False
print(any(l))  # 只要有一个元素为True就返回True

3.bool:判断数值的bool值为True还是False

print(bool(1))
print(bool(0))

4.bytes:转成二进制类型

s = 'hello'
print(bytes(s,encoding='utf-8'))

5.callable:判断是否为可调用对象(加括号执行代码)

l = [1,2,3]
def index():
    pass
print(callable(l))
print(callable(index))

6.chr、ord

print(chr(97))  # 将数字转换成ascii码表对应的字符
print(ord('a'))  # 将字符按照ascii表转成对应的数字

7.dir:会返回当前对象名称空间里面所有的名字(当前对象可调用的方法)

l = [1,2,3]
print(dir(l))

8.divmod:分页器

print(divmod(105,10))  # 第一个值除以第二个值,返回(结果,余数)
#可以利用方法来求总页数
total_num,more = divmod(901,10)  #909为数据总数,10为每页显示多少数据
if more:
    total_num += 1
print('总页数:',total_num)

9.enumerate:枚举

l = ['a','b']
for i,j in enumerate(l,1):  # 依次给数据编号,默认从0开始,可以添加参数从参数开始编号
    print(i,j)

10.eval、exec

s = "print('hello baby~')"
s1 ='''
x = 1
y = 2
print(x + y)
'''
eval(s)
exec(s)
exec(s1)  # 支持逻辑代码
eval(s1)  # 报错,eval不支持逻辑代码,只支持一些简单的python代码

11.globals、locals

def index():
    username = '我是局部名称空间里面的username'
    print(locals())  # 当前语句在哪个位置,就会返回哪个位置所存储的所有的名字
    print(globals())  # 无论在哪,查看的都是全局名称空间
index()

12.help:查看函数注释

def login():
    """
    注释
    :return:
    """
print(help(login))

13.isinstance

n = 1
print(type(n))
print(isinstance(n,list))  # 判断对象是否属于某个数据类型,传入你认为这个对象是什么类型

14.pow:次方运算

print(pow(2,3))  # 后一个值是前一个值几次方,计算结果,此处是求2的3次方的结果

15.round:求大概值,四舍五入

print(round(3.4))
print(round(3.5))

面向过程编程  

1.面向过程编程:

  就类似于设计一条流水线

好处:

  将复杂的问题流程化,从而简单化

坏处:

  可扩展性较差,一旦需要修改,整体都会受到影响

2.本质:

  就相当于按我们人类正常的思维模式,一步一步的解决问题

原文地址:https://www.cnblogs.com/francis1/p/11192070.html