迭代器、生成器、装饰器


迭代器

概念

1.迭代器的定义: 能被next调用,并不断返回下一个值的对象,叫做迭代器(对象)

2.迭代器的概念:
  迭代器指的是迭代取值的工具,迭代是一个重复的过程,
  每次重复都是基于上一次的结果而继续的,
  单纯的重复并不是迭代

3.迭代器的特征: 并不依赖索引,而通过next指针迭代所有数据,一次只取一个值,大大节省空间

4.dir: 获取当前类型对象中的所有成员

__iter__方法可判断该数据是否是可迭代数据

setvar = {"a","b","c","d"}
lst = dir(setvar) # 获取setvar对象中的所有成员
print(lst)
print(lst.__iter__()) # 如该数据不是可迭代类型数据会报错
'''
['__and__', '__class__', '__contains__', '__delattr__',
'__dir__', '__doc__', '__eq__', '__format__', '__ge__', 
'__getattribute__', '__gt__', '__hash__', '__iand__', '__init__',
'__init_subclass__', '__ior__', '__isub__', '__iter__', '__ixor__', 
'__le__', '__len__', '__lt__', '__ne__', '__new__', 
'__or__', '__rand__', '__reduce__', '__reduce_ex__', '__repr__',
'__ror__', '__rsub__', '__rxor__', '__setattr__', '__sizeof__', 
'__str__', '__sub__', '__subclasshook__', '__xor__', 'add',
'clear', 'copy', 'difference', 'difference_update', 'discard',
'intersection', 'intersection_update', 'isdisjoint', 'issubset',
'issuperset', 'pop', 'remove', 'symmetric_difference', 
'symmetric_difference_update', 'union', 'update']
'''

关于迭代器需要注意的点

可以以直接作用于for循环的数据类型有以下几种:
1.一类是集合数据类型,如list、tuple、dict、set、str等;

2.一类是generator,包括生成器和带yield的generator function。
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable,可迭代的意思就是可遍历、可循环。

for 循环能够遍历一切可迭代性数据的原因在于,底层调用了迭代器,通过next方法中的指针实现数据的获取

可迭代对象(不能够被next直接调用) -> 迭代器(可以被next直接调用的过程)

一个可迭代对象不一定是迭代器

一个迭代器就一定是一个可迭代对象

使用迭代器的好处:
1)如果使用列表,计算值时会一次获取所有值,那么就会占用更多的内存。而迭代器则是一个接一个计算。
2)使代码更通用、更简单。

定义一个迭代器

定义迭代器需要使用iter()方法

setvar = {"a","b","c","d"}
it = iter(setvar) # 将可迭代对象setvar变成了迭代器
print(it) # <set_iterator object at 0x000002142D1108B8>

判断一个迭代器

迭代器必须要有__iter__方法和__next__方法

setvar = {"a","b","c","d"}
it = iter(setvar) # 将可迭代对象setvar变成了迭代器
print(it) # <set_iterator object at 0x000002142D1108B8>

res = "__iter__" in dir(it) and "__next__" in dir(it)
'''
dir() 函数不带参数时,返回当前范围内的变量、方法和定义的类型列表;
带参数时,返回参数的属性、方法列表。如果参数包含方法__dir__(),
该方法将被调用。如果参数不包含__dir__(),该方法将最大限度地收集参数信息。
'''
print(res) #True

调用迭代器

next在调用迭代器中的数据时,是单向不可逆,一条路走到黑的过程
当没有可迭代的数据后,抛出一个StopIteration的异常,并且停止迭代

setvar = {"a","b","c","d"}
it = iter(setvar) # 将可迭代对象setvar变成了迭代器
res = next(it)
print(res) # c
res = next(it)
print(res) # d
res = next(it)
print(res) # a
res = next(it)
print(res) # b
res = next(it)
print(res) # 当没有可迭代的数据后,抛出一个StopIteration的异常,并且停止迭代

重置迭代器

重置迭代器,只需要再次调用iter()方法即可

it = iter(setvar) # 重置迭代器
res = next(it)
print(res)

使用isinstance()判断是否是可迭代对象/迭代器

"""Iterator 迭代器 Iterable 可迭代对象"""
from collections import Iterator,Iterable
it = iter(setvar)
res = isinstance(it,Iterator)
print(res)
res = isinstance(it,Iterable)
print(res)

除了next()也可以使用以下两种方式调用迭代器中的数据

# 1. for 循环
print("<=>")
for i in it:
    print(i)

# 2. for + next
print("<=>")
lst = [1,2,3,4,5,6,7,7,8,9,10]
it = iter(lst)
for i in range(10):
    res = next(it)
    print(res)

print(next(it))
print(next(it))

生成器

概念

生成器可以理解为一种数据类型,自动实现迭代器协议
在调用生成器运行的过程中,每次遇到yield时函数会暂停并保存当前所有的运行信息,返回yield的值。并在下一次执行next()方法时从当前位置继续运行

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。比如我要循环100万次,按py的语法,for i in range(1000000)会先生成100万个值的列表。但是循环到第50次时,我就不想继续了,就退出了。但是90多万的列表元素就白为你提前生成了。

for i in range(1000000):
    if i == 50: 
        break
    print(i)

在Python中,这种一边循环一边计算后面元素的机制,称为生成器:generator

表现形式:

生成器函数 带yield的函数(1、返回值 2、保留函数的运行状态)
next(t)  t.__ next__  t.send(可以给上一层的yield传值)**

 # 用生成器函数
 # yield 相当于return控制的是函数的返回值
 # x=yield的另外一个特性,接收send传过来的值,赋值给x
 def test():
     print("开始啦")
     first = yield # return 1   first = None
     print("第一次",first)
     yield 2
     print("第二次")
 t = test()
 print(test().__next__())
 res = t.__next__() # next(t)
 print(res)
 res = t.send("函数停留在first那个位置,我就是给first赋值的")
 print(res)
 '''
 输出结果
 开始啦
 None
 开始啦
 None
 第一次 函数停留在first那个位置,我就是给first赋值的
 '''

生成器表达式

print(sum(i for i in range(10000))) # 表达式一般用for循环 (i for i in range(10000))

a = [x * x for  x in range(10)]
print(a) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

1.生成器本质是迭代器,允许自定义逻辑的迭代器
2.迭代器和生成器区别:
 迭代器本身是系统内置的.重写不了.
 而生成器是用户自定义的,可以重写迭代逻辑
3.生成器可以用两种方式创建:
  (1)生成器表达式 (里面是推导式,外面用圆括号)
  (2)生成器函数 (用def定义,里面含有yield)
4.生成器表达式<=>元组推导式

生成器函数

yield 类似于 return
共同点在于:执行到这句话都会把值返回出去
不同点在于:yield每次返回时,会记住上次离开时执行的位置 , 下次在调用生成器 , 会从上次执行的位置往下走
而return直接终止函数,每次重头调用.
yield 6 和 yield(6) 2种写法都可以 yield 6 更像 return 6 的写法 推荐使用

生成器函数的基本语法

# 定义一个生成器函数
def mygen():
    print(111)
    yield 1

    print(222)
    yield 2
    
    print(333)
    yield 3

# 初始化生成器函数,返回生成器对象,简称生成器
gen = mygen() # gen是生成器对象(生成器)
print(isinstance(gen,Iterator)) # gen是一个迭代器

# 使用next调用
res = next(gen)
print(res)
res = next(gen)
print(res)
res = next(gen)
print(res)

关于生成器函数的执行流程

代码解析:
初始化生成器函数 -> 生成器(通过next调用)
第一次调用生成器
res = next(gen) => print(111) yield 1 保存当前代码状态14,并将1这个值返回 print(1) ,等待下一次调用
第二次调用生成器
res = next(gen) => 从上一次保存的状态14行继续向下执行
print(222) yield 2 保存当前代码状态17,并将2这个值返回 print(2) ,等待下一次调用
第三次调用生成器
res = next(gen) => 从上一次保存的状态17行继续向下执行
print(333) yield 3 保存当前代码状态20,并将3这个值返回 print(3) ,等待下一次调用
第四次调用生成器
因为没有更多yield返回数据了,所以直接报错.

生成器小知识点

生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator

send的用法

next和send区别:
next 只能取值
send 不但能取值,还能发送值
send注意点:
第一个 send 不能给 yield 传值 默认只能写None
最后一个yield 接受不到send的发送值
send 是给上一个yield发送值
def mygen():
    print("process start")
    res = yield 100
    print(res,"内部打印1")
    
    res = yield 200
    print(res,"内部打印2")
    
    res = yield 300
    print(res,"内部打印3")
    print("process end")

# 初始化生成器函数 -> 生成器
gen = mygen()
# 在使用send时,第一次调用必须传递的参数是None(硬性语法),因为第一次还没有遇到上一个yield
'''第一次调用'''
res = gen.send(None) #<=> next(gen)
print(res)
'''第二次调用'''
res = gen.send(101) #<=> next(gen)
print(res)
'''第三次调用'''
res = gen.send(201) #<=> next(gen)
print(res)
'''第四次调用, 因为没有更多的yield返回数据了,所以StopIteration'''
res = gen.send(301) #<=> next(gen)
print(res)
'''
代码解析:
初始化生成器函数,返回生成器对象
第一次调用时,
print("process start")
res = yield 100 记录当前代码状态81行,返回100,等待下一次调用
res = 100 print(100)

第二次调用时,
把101 发送给上一个yield保存的状态81行 res = 101 从81行继续往下走
print(101,"内部打印1")
res = yield 200 记录当前代码状态84行,返回200,等待下一次调用
res = 200 print(200)

第三次调用时,
把201 发送给上一个yield保存的状态84行 res = 201 从84行继续往下走
print(201,"内部打印2")
res = yield 300 记录当前代码状态87行,返回300,等待下一次调用
res = 300 print(300)
'''

yield from用法

将一个可迭代对象变成一个迭代器返回

def mygen():
    yield from ["Alan","Fly","Hurt","1dao"]
    
gen = mygen()
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))

装饰器

装饰器本质上就是一个python函数,他可以让其他函数在不需要做任何代码变动的前提下,增加额外的功能,装饰器的返回值也是一个函数对象。
装饰器的应用场景:比如插入日志,性能测试,事务处理,缓存等等场景。

现在我有一个需求,我想让你测试这个函数的执行时间,在不改变这个函数代码的情况下:

import time
 
def func1():
    print('in func1')
    time.sleep(1) # 模拟程序逻辑
 
start= time.time()
func1()
print(time.time()- start)
'''
输出:
in func
1.006669044494629
'''

封装成函数
传一个参数,函数名,就可以查看函数的执行时间了

import time
 
def func1():
    print('in func1')
    time.sleep(1) # 模拟程序逻辑
 
def timmer(f):
    start_time= time.time()
    f()
    end_time= time.time()
    print("运行时间为{}".format(end_time- start_time))
 
timmer(func1)
'''
输出:
in func
运行时间为-1.0123016834259033
'''

原来有100个函数,都是调用了func1(),现在测试函数,需要timeer(func1)。如果想测试所有调用地方的执行时间,那么需要修改100个函数位置,改成timeer(func1),太麻烦了。
不能更改原来的调用方式,同时需要加一个函数执行时间的功能。怎么办呢?
要无限接近于原来的调用方法,慢慢来
先来讲一个变量赋值的例子

a= 1
b= a
a= 2 #a重新赋值了,原来a的值不存在了。但是b还是等于原来a的值,也就是1
print(a,b) # 执行输出2,1

在这里插入图片描述
更改执行方式

import time
 
def func1():
    print('in func1')
    time.sleep(1) # 模拟程序逻辑
 
def timmer(f):
    start_time= time.time()
    f()
    end_time= time.time()
    print('此函数的执行时间为{}'.format(end_time- start_time))
 
f1= func1#定义f1等于func1函数
func1= timmer#定义func1等于timmer,等式计算右边的。此时func1被覆盖了,和原来的没有任何关系,f1还是等于原来的func1
func1(f1)#此时相当于执行 timmer(老的func1)
'''
输出:
in func1
此函数的执行时间为1.0001263618469238
'''

在精简一步

import time
 
def func1():
    print('in func1')
    time.sleep(1) # 模拟程序逻辑
 
def timmer(f):# f = func1
    def inner():
        start_time= time.time()
        f()#执行func1()
        end_time= time.time()
        print('此函数的执行时间为{}'.format(end_time- start_time))
    return inner#将inner函数返回给函数调用者timmer(func1)
 
a= timmer(func1)#等式计算右边的,将func1函数传给timmer函数
a()#此时相当于执行了inner函数
'''
in func1
此函数的执行时间为1.000577449798584
'''

1.装饰器基础版

最终版本

import time
 
def timmer(f):# 2接收参数.f = func1
    def inner():
        start_time= time.time()#5.进入inner函数
        f()#6.执行f(),也就是原来的func1函数。虽然func1被覆盖了,但是之前的值还存在。请参考上面a,b赋值的例子
        end_time= time.time()#10 获取当前时间
        print('此函数的执行时间为{}'.format(end_time- start_time))#11.输出差值
    return inner#3.将inner函数返回给函数调用者timmer(func1),此时程序结束,继续执行func1()
 
def func1():#7.进入函数
    print('in func1')# 8.打印输出
    time.sleep(1) # 9.等待1秒
 
func1= timmer(func1)#1.等式计算右边的,将func1函数传给timmer函数,此时func1被覆盖了,原来func1的不存在了。
func1()#4.这里的func1是全新的func1,就是上面的赋值,此时相当于执行 inner函数

代码从上至下执行,先加载函数变量timmer和func1。在执行上文说的第1步以及后面的,请看数字。

但是加这一步,还是不太方便。那么python给这种情况,加了一个语法糖@
语法糖指那些没有给计算机语言添加新功能,而只是对人类来说更"甜蜜"的语法

@符号作用:

(1) 可以自动把@符号下面的函数当成参数传递给装饰器
(2) 把新函数返回,让新函数去替换旧函数,以实现功能上的扩展(基于原函数)

2.带@符号的装饰器

import time
 
def timmer(f):# f = func1
    def inner():
        start_time= time.time()
        f()#执行func1()
        end_time= time.time()
        print('此函数的执行时间为{}'.format(end_time- start_time))
    return inner#将inner函数返回给函数调用者timmer(func1)
 
#语法糖@
@timmer #相当于 func1 = timmer(func1) #这一步就已经覆盖了
def func1():
    print('in func1')
    time.sleep(1) # 模拟程序逻辑
 
func1()#相当于执行了inner()函数

想测试谁,前面加@装饰器函数
装饰器利用return制造了一个假象,func1()执行,其实是执行了inner()
func1()已经把原来的func1()给覆盖了

但装饰器,还不够完整,函数不能传参数
举个例子

def func2(a1,b1):#4.执行函数,接收位置参数a1=1,b1=2
    print(a1,b1)#5. 输出1,2
def func3(a,b):#2. 接收参数,a=1,b=2
    func2(a,b)#3. 执行函数func2(1,2)
func3(1,2)#1.传位置参数1,2

func3执行一遍后,将a,b的值,传给a1,b1了
上面装饰器的例子,func1,要传2个参数a,b

import time
 
def timmer(f):
    def inner(a,b):
        start_time= time.time()
        f(a,b)
        end_time= time.time()
        print('此函数的执行时间为{}'.format(end_time- start_time))
    return inner
 
@timmer
def func1(a,b):
    print('in func1 {}{}'.format(a,b))
    time.sleep(1) # 模拟程序逻辑
 
func1(1,2)
'''
输出:
in func1 12
此函数的执行时间为1.0006024837493896
'''

3.装饰器的嵌套

在这里插入图片描述

# 装饰器的嵌套
def kuozhan1(func):
    def newfunc():    
        print("厕所前,人模狗样1")
        func()
        print("厕所后,斯文败类2")
    return newfunc
    
def kuozhan2(func):
    def newfunc():
        print("厕所前,洗洗手3")
        func()
        print("厕所后,簌簌口4")
    return newfunc
    
@kuozhan2
@kuozhan1
def func():
    print("我是葛龙0")

func()

4.用装饰器扩展带有参数的原函数

在这里插入图片描述

def kuozhan(func):
    def wc(who,where):
        print('{}在{}钱,萎靡不振'.format(who,where))
        func(who,where)
        print('{}在{}后,油光满面'.format(who,where))
    return wc

@kuozhan
def newfunc(who,where):
    print('{}在{}用餐'.format(who,where))

newfunc('吴X凡','厕所') # newfunc = wc=> func("吴X凡","厕所") <=> wc("吴X凡","厕所")

5.用装饰器扩展带有参数和返回值的原函数

在这里插入图片描述

# 5.用装饰器扩展带有参数和返回值的原函数
def kuozhan(func):
    def newfunc(*args,**kwargs):
        print("厕所前,饥肠辘辘")
        res = func(*args,**kwargs)
        print("厕所后,酒足饭饱")
        return res
    return newfunc
        
@kuozhan
def func(*args,**kwargs):
    lst = []
    dic = {"gaoxuefeng":"高雪峰","sunzhihe":"孙致和","gelong":"戈隆"}
    
    # 解手的地点遍历出来
    for i in args:
        print("拉屎的地点:",i)
    for k,v in kwargs.items():
        if k in dic:
            strvar = dic[k] + "留下了" + v + "黄金"
            lst.append(strvar)
    return lst

lst = func("电影院","水下",gaoxuefeng = "15g",sunzhihe = "15顿",gelong="15斤")
print(lst)

6.用类装饰器扩展原函数

在这里插入图片描述

# 6.用类装饰器来拓展原函数
class Kuozhan():
    def __call__(self,func):        
        return self.kuozhan2(func)
    
    def kuozhan1(func):
        def newfunc():
            print("厕所前,老实巴交")
            func()
            print("厕所后,咋咋乎乎")
        return newfunc

    def kuozhan2(self,func):
        def newfunc():
            print("厕所前,唯唯诺诺")
            func()
            print("厕所后,重拳出击")
        return newfunc

# 方法一
""""""
@Kuozhan.kuozhan1
def func():
    print("厕所进行中....")

func()
# 方法二
@Kuozhan()  # @obj => obj(func)
def func():
    print("厕所进行中....")

func()

7.带有参数的函数装饰器

在这里插入图片描述在这里插入图片描述

# 7.带有参数的函数装饰器
def outer(num):
    def kuozhan(func):    
        def newfunc1(self):
            print("厕所前,干净整齐")
            func(self)
            print("厕所后,一片狼藉")
        
        def newfunc2(self):
            print("厕所前,大腹便便")
            func(self)
            print("厕所后,满口雌黄")
            
        if num == 1:
            return newfunc1
        elif num == 2:
            return newfunc2
        elif num == 3:
            # 把func3方法变成属性
            return "我是女性"

    return kuozhan

class MyClass():
    
    @outer(1) # => (1) kuozhan(func1) => newfunc1   (2) 发动技能做替换  func1 = newfunc1 
    def func1(self):
        print("向前一小步,文明一大步")
    
    @outer(2) # => (2) kuozhan(func2) => newfunc2   (2) 发动技能做替换  func2 = newfunc2 
    def func2(self):
        print("来也冲冲,去也冲冲")
    
    @outer(3) # => (3) kuozhan(func3) => "我是女性" (2) 发动技能做替换  func3 = "我是女性" 
    def func3(self):
        print("hahaha,lalala")

obj = MyClass()

obj.func1() # <=> newfunc1
obj.func2()
# obj.func3() error
print(obj.func3)
print(MyClass.func3)

代码解析

1.套上outer这层函数就是为了保留1 2 3 三个参数,可以在闭包函数中使用

2.调用outer 结束之后,才是返回正常的装饰器kuozhan

3.此刻,@符第一次发动技能,把func1当成参数传递给kuozhan , 返回newfunc1

4.然后,@符第二次发动技能,将返回的newfunc1替换原来func1

if num == 1 返回闭包函数newfunc1

if num == 2 返回闭包函数newfunc2

来做替换

obj.func3 在执行时,装饰器已经把func3变成了属性func3 = "我是女性"

所以obj.func3 或者 MyClass.func3 都是"我是女性" 字符串.

obj.func3 => return "我是女性"

MyClass.func3 => return "我是女性"

8.带有参数的类装饰器

如果参数是1,就为当前类添加成员属性和方法

如果参数是2,就把原方法run变成属性
在这里插入图片描述

# 8.带有参数的类装饰器
"""
如果参数是1,就为当前类添加成员属性和方法
如果参数是2,就把原方法run变成属性
"""
class Kuozhan():
    money = "贵族厕所,每小时1000元,贵族厕所欢迎您来,欢迎您再来"
    
    def __init__(self,num):
        self.num = num
    
    def __call__(self,cls):
        if self.num == 1:
            return self.newfunc1(cls)
            
        elif self.num == 2:
            return self.newfunc2(cls)
    
    def ad(self):
        print("贵族茅厕,茅厕中的百岁山")
    
    def newfunc1(self,cls):
        def newfunc():
            # 为当前cls这个类,添加属性
            cls.money = Kuozhan.money
            # 为当前cls这个类,添加方法
            cls.ad = Kuozhan.ad
            return cls()
        return newfunc
    
    def newfunc2(self,cls):
        def newfunc():
            if "run" in cls.__dict__:
                # 调用类中的方法,得到对应的返回值
                res = cls.run()
                # 把返回值重新赋值到run属性上
                cls.run = res# cls.run = "亢龙有悔"
                
                return cls()
        return newfunc

# 参数1
@Kuozhan(1) 
class MyClass():
    def run():
        return "亢龙有悔"
obj = MyClass()
print(obj.money)
obj.ad()

# 参数2
@Kuozhan(2)
class MyClass():
    def run():
        return "亢龙有悔"

obj = MyClass()
print(obj.run)

注意点:把类当做参数传递到函数中???

class Ceshi():
    ad = 10
obj = Ceshi()
print(obj.ad) # 10

def func(cls):
    cls.money = 100
    return cls()
    
# 把类当成参数传递到函数当中,在局部空间中,形成独立的副本
obj = func(Ceshi)
# 把类变量Ceshi 变成字符串
Ceshi = "abcde"
# 发现局部空间中类对象中的成员,并没有受到影响
print(obj.money) # 100

面试题,手写一个装饰器

写装饰器,约定俗成,函数名为wrapper
第一步

def wrapper(func):
    def inner():
        func()
    return inner

第二步

def wrapper(func):
    def inner(*args,**kwargs):
        func(*args,**kwargs)
    return inner

第三步

def wrapper(func):
    def inner(*args,**kwargs):
        '''被装饰函数之前'''
        ret= func(*args,**kwargs)
        '''被装饰函数之后'''
        return ret
    return inner

完整的

def wrapper(func):
    def inner(*args,**kwargs):
        '''被装饰函数之前'''
        ret= func(*args,**kwargs)
        '''被装饰函数之后'''
        return ret
    return inner
 
@wrapper
def func(*args,**kwargs):
    print(args,kwargs)
    return 666
 
print(func())
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值