博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

第七节 Python基础之迭代器,生成器,装饰器

Posted on 2017-02-13 22:26  Jasonhy  阅读(102)  评论(0)    收藏  举报

    在Python里,迭代器和生成器是比较常用的,在进行学习这两个概念之前,我们先来看一个简单的例子:

  # LIST是一个列表,通过for循环来遍历里面的每个元素,我们常说可迭代对象才能被for循环遍历,那么LIST也就是列表就是一个可  迭代对象了?那么我们就得来看什么是迭代器了
  LIST = [1,2,3,4]
  for item in LIST:
      print(item)

    迭代器对象:就是遵守迭代器协议的对象,那么什么是迭代器协议呢?

   迭代器协议:对象必须提供一个__next__()方法,执行该方法要么返回迭代下一个,要么就引起一个StopIteration异常,以终止迭代(特点:只能往后走不能往前退)

   在上面的案例中,我们说for循环遍历的是可迭代对象,那么什么才是可迭代对象呢?

   可迭代对象:实现迭代协议的对象,也就是说这个对象有__next__()方法,但是我们在查一个列表的方法中,并没有发现有__next__()这个方法,那么列表就不是可迭代对象了,既然不是可迭代对象,那么为什么for循环能对它进行遍历呢?我们发现列表中有__iter__()这个方法,这个方法就是将列表变成一个可迭代对象,也就是说for循环的时候,现将列表变成一个可迭代对象再进行遍历的,与此同时,字符串,元组,字典,集合,文件对象这些都不是可迭代对象,只不过在进行for循环的时候,会调用它们内部的__iter__()方法,将它们变成可迭代对象了.

   那么生成器呢?我们可以说,生成器就是一种数据类型,而这种数据类型自动实现了迭代器协议;也就是说,生成器通过生成器函数,我们通过常规的def语句来定义这么一个函数,但是我们不用return作为返回值,而是使用yield一次返回一个结果,在每个结果之间挂起和继续它们的状态,从而自动的实现迭代协议,yield是一个语法糖,内部实现支持了迭代器协议,同时yield内部是一个状态机,维护着挂起和继续的状态,如案例:

def test_generator(num):
    i = 0
    while i < num:
        print("yield之前",i)
        yield i
        i += 1
        print("yield之后",i)
    print("循环结束")
gt = test_generator(10)     # 调用这个函数了,但是并没有输出
print(gt.__next__())
'''
以上输出结果是:
yield之前 0
0
'''
print(gt.__next__())
'''
以上输出结果是:
yield之后 1
yield之前 1
1
'''

   从上面这个案例中,我们发现当我在调用生成器函数的时候,里面的输出代码并没有执行,而是返回一个生成器对象,当我们在调用__next__()的之后,生成器函数才开始执行,执行到yield语句的时候就停止了,返回值就是yield后面的值,当我们再次调用__next__()的时候,函数将接着上一次yield停止的地方继续执行病倒下一个yield处就有停止了.

   那么生成器函数的执行方式有哪些呢?

   ①调用__next__()方法

   ②通过内置函数next()调用生成器函数

   ③通过send(参数)方法:将参数传给yield

      send(参数):

def test_generator(num):
    i = 0
    while i < num:
        val = yield i
        print("val等于:",val)
        i += i
gt = test_generator(10)
print(gt.__next__())
print(gt.__next__())
'''
关于send()函数:在调用send传入非None前,生成器必须处于挂起状态,否则会抛出异常
TypeError: can't send non-None value to a just-started generator
也就是说,第一次调用,要使用next()语句或send(None),因为没有yield来接收这个值
'''
print(gt.send("传入值"))
gt.close()  # 关闭生成器

 

 

 

   在上面我们说到的生成器函数中,实际上生成器函数是生成器的一种表现形式,那么另外一种表现形式就是生成器表达式,我们先来看我们熟悉的案例:

# 三元表达式
name = "Jasonhy"
res = 18 if name == "Jasonhy" else 20
print(res)
# 列表解析
item = ["奇数%d"% i for i in range(10) if i%2]
print(item)

 

     而生成器表达式有点像列表解析,只不过是将[]换成了():

gt = ("奇数%d"% i for i in range(10) if i%2)
print(gt)       # 输出结果是:<generator object <genexpr> at 0x0069CF00>
print(gt.__next__())    # 输出结果是:奇数1

     列表解析和生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存

 

 

    生成器的优缺点:

    ①延迟计算,一次返回一个结果,也就是说它不会一次生成所有结果,这对于大数据处理处理,将会非常有用

    ②有效的提高代码的可读性

    ③生成器只能遍历一次,案例:

# 统计人口占中人口的百分比
def get_population_num(filename):
    with open(filename) as file:
        for num in file:
            p = eval(num)
            yield p["population"]
gt = get_population_num("population_statistics")
all_p = sum(gt)
for p in gt:
    print(p / all_p)    # 没有任何输出,原因是生成器之遍历一次,当我执行sum的时候,就遍历了我们的生成器,当我们再次for循环去遍历我们的生成器的时候,将不会有任何记录
#人口信息
{'name':'广州','population':3000000} {'name':'深圳','population':2000000} {'name':'贵州','population':150000} {'name':'贵阳','population':100000}

    装饰器:  说白了,也就是函数,它的作用就是为其他函数附加新的功能,但是需要遵守两个原则:  

   ①不修改被修饰函数的源代码

   ②不修改被修饰函数的调用方式

   一个装饰器我们可以说是由高阶函数+函数嵌套+闭包完成的;高阶函数我们在之前讲过,也就是函数接收的参数是一个函数名,或者函数的返回值是一个函数名,只要满足其中一个就可以.

# 高阶函数
import time
def sleep_func():
    '''
    我们定义了一个睡觉的函数
    现在想知道这个函数睡了多久
    :return:
    '''
    time.sleep(1)
    print("我睡多久了???")

def test_sleep(func):
    start_time = time.time()
    func()
    end_time = time.time()
    print("你睡了%d秒"%(end_time - start_time))
test_sleep(sleep_func)
'''
我们定义了一个test_sleep高阶函数,来测试sleep_func函数的运行时间,通过test_sleep来调用sleep_func,改变了调用方式,所以test_sleep不是装饰器
'''

    函数嵌套比较好理解,这里就不说了,那么什么是闭包呢?我们先来看一个例子:

def outer():
    '''
    我们在outer函数内嵌套了一个inner函数,inner作为一个函数被outer
    返回
    :return:
    '''
    x = 1
    def inner():
        print(x)
    return inner
o = outer()
'''
我们从变量的生命周期来看,变量x是函数outer的一个本地变量,这就意味只有outer函数正在运行的时候才会存在,所以我们认为没法再函数outer返回之后继续调用函数inner,所以在函数inner被调用的时候,变量x应该早已不存在,应该会报错,但是事实上并没有报错,还会将变量x的值打印出来,而这种特性我们可以叫做函数的闭包
我们可以这样理解,嵌套定义在非全局作用域里的函数能够记住它在被定义的时候所处的命名空间
每次函数outer被调用的时候,函数inner都会被重新定义
'''
o()

 

     闭包单独拿出来就是一个非常强大的功能,在某些方面,我们可以吧当做一个类似面向对象的技术:outer像是给inner服务的构造器,x像是一个私有变量.

import time
def sleep_func():
    time.sleep(1)
    print("我睡了多久了?")
def adorner(func):
    '''
    定义了一个装饰器
    :param func:
    :return:
    '''
    def wrapper(*args,**kwargs):
        start_time = time.time()
        func(*args,**kwargs)
        end_time = time.time()
        print("您睡了%d秒"%(end_time - start_time))
    return wrapper
sleep_func = adorner(sleep_func)
sleep_func()

    装饰器案例: 需求是,比如我们之前在获取一个首页数据的时候,是不需要登录验证的,现在,需求改变了,就是只有登录才能进入首页,原来的代码不能更改,这个时候我们就需要装饰器来完成了

# 登录验证功能
USER_INFO = [
    {"name":"A","pwd":"123"},
    {"name":"B","pwd":"123"},
    {"name":"C","pwd":"123"},
    {"name":"D","pwd":"123"},
]
# 定义一个变量 来记录用户是否登录 如果已经登录了,那就不用登录
USER_LOGIN = {"name":None,"is_logon":False}
def auth_login(func):
    def wrapper(*args,**kwargs):
        while True:
            if USER_LOGIN["name"] and USER_LOGIN["is_login"]:
                res = func(*args,**kwargs)
                return res
            else:
                user_name = input("请输入用户名")
                passwd = input("请输入密码")
                for user_info in USER_INFO:
                    if user_name == user_info["name"] and passwd == user_info["pwd"]:
                        USER_LOGIN["name"] = user_name
                        USER_LOGIN["is_login"] = True
                        res = func(*args,**kwargs)
                        return res
                else:
                    print("用户名或密码错误")
    return wrapper
@auth_login
def home_page():
    print("欢迎来到Jasonhy博客")
@auth_login
def look_articles():
    print("您正在查看装饰器功能")

home_page()
look_articles()