PYTHON 之 迭代器、生成器、闭包、装饰器、属性

可迭代对象iterable ---------迭代器iterator   的关系图

1.迭代(循环)器 iterator

我们已经知道,可以直接作用于for循环的数据类型有以下几种:

  • 一类是集合数据类型,如list,tuple,dict,set,str等

  • 一类是generator,包括生成器和带yield的generator function;生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。

这些可以直接作用于for 循环的对象统称为可迭代对象:Iterable

可以使用isinstance()判断一个对象是否为可Iterable对象

>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False
  • 可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。

可以使用isinstance()判断一个对象是否是Iterator对象:

>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False

生成器都是Iterator对象,但list、dict、str虽然是Iterable(可迭代对象),却不是Iterator(迭代器)。

把list、dict、str等Iterable变成Iterator可以使用iter()函数:

>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True
  • 问题? 为什么list、dict、str等数据类型不是Iterator?
  • 因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

s='hello'     #字符串是可迭代对象,但不是迭代器
l=[1,2,3,4]     #列表是可迭代对象,但不是迭代器
t=(1,2,3)       #元组是可迭代对象,但不是迭代器
d={'a':1}        #字典是可迭代对象,但不是迭代器
set={1,2,3}     #集合是可迭代对象,但不是迭代器
# *************************************
f=open('test.txt') #文件是可迭代对象,是迭代器
 
#如何判断是可迭代对象,只有__iter__方法,执行该方法得到的迭代器对象。
# 及可迭代对象通过__iter__转成迭代器对象
from collections import Iterator  #迭代器
from collections import Iterable  #可迭代对象
 
print(isinstance(s,Iterator))     #判断是不是迭代器
print(isinstance(s,Iterable))       #判断是不是可迭代对象
 
#把可迭代对象转换为迭代器
print(isinstance(iter(s),Iterator))
  • 如何判断是可迭代对象:只有__iter__方法,执行该方法得到的迭代器对象。
  • 把可迭代对象转换为迭代器:iter(s)

2.生成器 generator

  • 通过列表生成式,我们可以直接创建一个列表,但是,受到内存限制,列表容量肯定是有限的,而且创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

  • 如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。
      
      在Python中,这种一边循环一边计算的机制,称为生成器:generator

  • 生成器是一个特殊的程序,可以被用作控制循环的迭代行为,python中生成器是迭代器的一种,使用yield返回值函数,每次调用yield会暂停,而可以使用next()函数和send()函数恢复生成器

  • 生成器类似于返回值为数组的一个函数,这个函数可以接受参数,可以被调用,但是,不同于一般的函数会一次性返回包括了所有数值的数组,生成器一次只能产生一个值,这样消耗的内存数量将大大减小,而且允许调用函数可以很快的处理前几个返回值,因此生成器看起来像是一个函数,但是表现得却像是迭代器

python中的生成器

python提供了两种基本的方式

  • 生成器函数:也是用def定义的,利用关键字yield一次性返回一个结果,阻塞,重新开始

  • 生成器表达式:返回一个对象,这个对象只有在需要的时候才产生结果要创建一个generator,有很多种方法,第一种方法很简单,只有把一个列表生成式的[]中括号改为()小括号,就创建一个generator

2.1生成器表达式
  • 生成器表达式来源于迭代和列表解析的组合,生成器和列表解析类似,但是它使用尖括号而不是方括号
#列表生成式
lis = [x*x for x in range(10)]
print(lis)
#生成器
generator_ex = (x*x for x in range(10))
print(generator_ex)
 
结果:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
<generator object <genexpr> at 0x000002A4CBF9EBA0>

那么创建lis和generator_ex,的区别是什么呢?从表面看就是[ ]和(),但是结果却不一样,一个打印出来是列表(因为是列表生成式),而第二个打印出来却是<generator object at 0x000002A4CBF9EBA0>,那么如何打印出来generator_ex的每一个元素呢?

如果要一个个打印出来,可以通过next()函数获得generator的下一个返回值:

#生成器
generator_ex = (x*x for x in range(10))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
结果:
0
1
4
9
16
25
36
49
64
81
Traceback (most recent call last):
 
  File "列表生成式.py", line 42, in <module>
 
    print(next(generator_ex))
 
StopIteration
2.2 生成器函数
# 函数有了yield之后,函数名+()就变成了生成器
# return在生成器中代表生成器的中止,直接报错
# next的作用是唤醒并继续执行
# send的作用是唤醒并继续执行,发送一个信息到生成器内部
'''生成器'''
 
def create_counter(n):
    print("create_counter")
    while True:
        yield n
        print("increment n")
        n +=1
 
gen = create_counter(2)
print(gen)
print(next(gen))
print(next(gen))
 
结果:
<generator object create_counter at 0x0000023A1694A938>
create_counter
2
increment n
3
Process finished with exit code 0

3.闭包(closure)

  • 当一个函数在内部定义函数,并且内部的函数应用外部函数的参数或者局部变量,当内部函数被当做返回值的时候,相关参数和变量保存在返回的函数中,这种结果,叫闭包
  • 上面定义的myF4是一个标准闭包结构
  • 闭包关键特征:其变量为nonlocal类型
# 闭包常见坑
def count():
    # 定义列表,列表里存放的是定义的函数
    fs = []
    for i in range(1,4):
        # 定义了一个函数f
        # f是一个闭包结构
        def f():
            return i*i
        fs.append(f)
    return fs

f1,f2,f3 = count()
print(f1())
print(f2())
print(f3())

输出结果:
9
9
9

出现的问题:

  • 造成上述状况的原因是,返回函数引用了变量i, i并非立即执行,而是等到三个函数都返回的时候才统一使用,此时i已经变成了3,最终调用的时候,都返回的是 3*3
  • 此问题描述成:返回闭包时,返回函数不能引用任何循环变量
  • 解决方案: 再创建一个函数,用该函数的参数绑定循环变量的当前值,无论该循环变量以后如何改变,已经绑定的函数参数值不再改变

# 修改上述函数
def count2():
    def f(j):
        def g():
            return j*j
        return g
    fs = []
    for i in range(1,4):
        fs.append(f(i))
    return fs

f1,f2,f3 = count2()
print(f1())
print(f2())
print(f3())

输出结果:
1
4
9

4.装饰器

def hello():
    print("Hello world")
    
hello()

输出:
Hello world

f = hello
f()

输出:
Hello world

# f和hello是一个函数
print(id(f)) 
print(id(hello))

print(f.__name__)
print(f.__name__)

输出:
139982101828744
139982101828744
hello
hello

现在有新的需求:

对hello功能进行扩展,每次打印hello之前打印当前系统时间
而实现这个功能又不能改动现有代码 ==>使用装饰器

装饰器(Decorator)

  • 在不改动函数代码的基础上无限制扩展函数功能的一种机制,本质上讲,装饰器是一个返回函数的高阶函数
    -装饰器的使用: 使用@语法, 即在每次要扩展到函数定义前使用@+函数名

  • python装饰器本质上就是一个函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外的功能,装饰器的返回值也是一个函数对象(函数的指针)。装饰器函数的外部函数传入我要装饰的函数名字,返回经过修饰后函数的名字;内层函数(闭包)负责修饰被修饰函数。从上面这段描述中我们需要记住装饰器的几点属性,以便后面能更好的理解:

- (1)实质: 是一个函数

- (2)参数:是你要装饰的函数名(并非函数调用)

- (3)返回:是装饰完的函数名(也非函数调用)

- (4)作用:为已经存在的对象添加额外的功能

- (5)特点:不需要对对象做任何的代码上的变动

4.1 函数的函数装饰器

通用语法糖格式:

def decorator(func):
    def wrapper(*args, **kwargs):
        ........#增加新装饰功能代码
        return func(*args, **kwargs)
    return wrapper

在上面代码中 func是我要装饰器的函数,我想用装饰器显示func函数运行的时间。@decorator这个语法相当于 执行 func = decorator(func),为func函数装饰并返回。在来看一下我们的装饰器函数 - decorator,该函数的传入参数是func (被装饰函数),返回参数是内层函数。这里的内层函数-wrapper,其实就相当于闭包函数,它起到装饰给定函数的作用,wrapper参数为*args, **kwargs。*args表示的参数以列表的形式传入;**kwargs表示的参数以字典的形式传入

例子如下:
# 任务:
# 对hello函数进行功能扩展,每次执行hello万打印当前时间

import time

# 高阶函数,以函数作为参数
def printTime(f):
    def wrapper(*args, **kwargs):
        print("Time: ", time.ctime())
        return f(*args, **kwargs)
    return wrapper
# 上面定义了装饰器,使用的时候需要用到@, 此符号是python的语法糖
@printTime
def hello():
    print("Hello world")
    
hello()

输出:
Time: Mon Apr 2 21:14:52 2018
Hello world

# 装饰器的好处是,一点定义,则可以装饰任意函数
# 一旦被其装饰,则则把装饰器的功能直接添加到定义函数的功能上

@printTime
def hello2():
    print("今天很高兴,被老板揪着讲课了")
    print("还可以由很多的选择")

hello2()

输出:
Time: Mon Apr 2 21:14:52 2018
Hello world

# 装饰器的好处是,一点定义,则可以装饰任意函数
# 一旦被其装饰,则则把装饰器的功能直接添加到定义函数的功能上

@printTime
def hello2():
    print("今天很高兴,被老板揪着讲课了")
    print("还可以由很多的选择")

hello2()

输出:
Time: Mon Apr 2 21:17:50 2018
今天很高兴,被老板揪着讲课了
还可以由很多的选择

# 上面对函数的装饰使用了系统定义的语法糖
# 下面开始手动执行下装饰器
# 先定义函数

def hello3():
    print("我是手动执行的")
    
hello3()

hello3 = printTime(hello3)
hello3()

f = printTime(hello3)
f()
# 作业:
# 解释下面的执行结果

输出:
我是手动执行的
Time: Mon Apr 2 21:22:39 2018
我是手动执行的
Time: Mon Apr 2 21:22:39 2018
Time: Mon Apr 2 21:22:39 2018
我是手动执行的

4.2类方法的函数装饰器
  • 类方法的函数装饰器和函数的函数装饰器类似。
import time

def decorator(func):
    def wrapper(me_instance):
        start_time = time.time()
        func(me_instance)
        end_time = time.time()
        print(end_time - start_time)
    return wrapper

class Method(object):

    @decorator 
    def func(self):
        time.sleep(0.8)

p1 = Method()
p1.func() # 函数调用
  • 对于类方法来说,都会有一个默认的参数self,它实际表示的是类的一个实例,所以在装饰器的内部函数wrapper也要传入一个参数 - me_instance就表示将类的实例p1传给wrapper,其他的用法都和函数装饰器相同。
4.3类装饰器
  • 前面我们提到的都是让 函数作为装饰器去装饰其他的函数或者方法,那么可不可以让 一个类发挥装饰器的作用呢?答案肯定是可以的,一切皆对象嚒,函数和类本质没有什么不一样。类的装饰器是什么样子的呢?
class Decorator(object):
    def __init__(self, f):
        self.f = f
    def __call__(self):
        print("decorator start")
        self.f()
        print("decorator end")

@Decorator
def func():
    print("func")

func()
  • 这里有注意的是:call()是一个特殊方法,它可将一个类实例变成一个可调用对象:
  • p = Decorator(func) # p是类Decorator的一个实例
  • p() # 实现了__call__()方法后,p可以被调用

要使用类装饰器必须实现类中的__call__()方法,就相当于将实例变成了一个方法。

5.属性

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值