JB的Python之旅-生成器、闭包、装饰器

栏目: Python · 发布时间: 6年前

内容简介:昨天突然在某群看到这么一段对话:jb关注的重点是反正,jb是不懂的,如有错误,欢迎交流,大神请轻喷;

昨天突然在某群看到这么一段对话:

JB的 <a href='https://www.codercto.com/topics/20097.html'>Python</a> 之旅-生成器、闭包、装饰器
JB的Python之旅-生成器、闭包、装饰器
JB的Python之旅-生成器、闭包、装饰器

jb关注的重点是 生成器闭包装饰器元编程 ,显然,作为一位测试同学,会点py,可以让饭更香,但是,如果简历上写着精通py,那是否真的懂这些呢?

反正,jb是不懂的,如有错误,欢迎交流,大神请轻喷;

JB的Python之旅-生成器、闭包、装饰器

生成器 & 迭代器

要说生成器,得先知道生成器是解决什么问题的;

相信大家都用过列表,假如list里面有100W个元素,而只需要前面几个元素,而 list对象会一次性把所有元素都加载到内存 ,这样就会造成后面的元素所占的内存空间是白白浪费的;

那有没有方案解决这问题?有,那就是 迭代器

迭代器

迭代器,顾名思义就是用来迭代操作的对象,跟list一样,可以迭代获取每一个元素,跟list区别在于, 构建迭代器的时候,不像列表把所有元素一次性加载到内存,而是以一种延迟计算(lazy evaluation)方式返回元素

而迭代器有两个基本的方法: inter() next()

实现了 __iter____next__ 方法的对象都称为迭代器,在调用 next() 的时候返回下一个值,如果容器中没有更多元素了,则抛出StopIteration异常;

import sys         # 引入 sys 模块
 
list=[1,2,3,4]
it = iter(list)    # 创建迭代器对象
 
while True:
    try:
        print (next(it))
    except StopIteration:
        sys.exit()
复制代码

生成器

Python 中,使用了 yield 的函数被称为生成器;

在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行;

例子,生成器:

def jb(N):
    for i in range(N):
        yield i**2

for item in jb(5):
    print(item)
复制代码

普通函数:

def jb(N):
    res = []
    for i in range(N):
        res.append(i*i)
    return(res)

for item in jb(5):
    print(item)
复制代码

闭包

作用域

作用域是程序运行时变量可被访问的范围,定义在函数内的变量是局部变量,局部变量的作用范围只能是函数内部范围内,它不能在函数外引用;

def foo():
    num = 10 # 局部变量
print(num)  # NameError: name 'num' is not defined
复制代码

定义在模块最外层的变量是全局变量,它是全局范围内可见的,当然在函数里面也可以读取到全局变量的。例如:

num = 10 # 全局变量
def foo():
    print(num)  # 10
复制代码

嵌套函数

函数不仅可以定义在模块的最外层,还可以定义在另外一个函数的内部,像这种定义在函数里面的函数称之为 嵌套函数

def print_msg():
    # print_msg 是外围函数
    msg = "jb is here"

    def printer():
        # printer是嵌套函数
        print(msg)
    printer()
# 输出 jb is here
print_msg()
复制代码

闭包的定义

闭包的概念就是当我们在函数内定义一个函数时,这个内部函数使用了外部函数的临时变量,且外部函数的返回值是内部函数的引用时,称之为闭包;
复制代码
# 一个简单的实现计算平均值的代码
 
def get_avg():
    scores = []  # 外部临时变量
 
    def inner_count_avg(val):  # 内部函数,用于计算平均值
        scores.append(val)  # 使用外部函数的临时变量
        return sum(scores) / len(scores)  # 返回计算出的平均值
 
    return inner_count_avg  # 外部函数返回内部函数引用
 
avg = get_avg()
print(avg(10))  # 10
print(avg(11))  # 10.5

复制代码

相加的例子:

def adder(x):
    def wrapper(y):
        return x + y
    return wrapper

# adder5对象是adder返回的闭包对象
adder5 = adder(5)
# 输出 15
adder5(10)
# 输出 11
adder5(6)
复制代码

装饰器

闭包的实际使用,大多数是用于装饰器,而这是什么东西?

假设程序实现了 say_hello()say_goodbye() 两个函数;

def say_hello():
    print "hello!"
    
def say_goodbye():
    print "hello!"  # bug here

if __name__ == '__main__':
    say_hello()
    say_goodbye()
复制代码

但是在实际调用中,发现程序出错了,上面的代码打印了两个hello,经过调试发现是 say_goodbye() 出错了;

负责人要求调用每个方法前都要记录进入函数的名称,比如这样:

Copy
[DEBUG]: Enter say_hello()
Hello!
[DEBUG]: Enter say_goodbye()
Goodbye!
复制代码

好,小A是个毕业生,他是这样实现的。

def say_hello():
    print "[DEBUG]: enter say_hello()"
    print "hello!"

def say_goodbye():
    print "[DEBUG]: enter say_goodbye()"
    print "hello!"

if __name__ == '__main__':
    say_hello()
    say_goodbye()
复制代码

很low吧? 嗯是的;

小B工作有一段时间了,他告诉小A可以这样写;

def debug():
    import inspect
    caller_name = inspect.stack()[1][3]
    print "[DEBUG]: enter {}()".format(caller_name)   

def say_hello():
    debug()
    print "hello!"

def say_goodbye():
    debug()
    print "goodbye!"

if __name__ == '__main__':
    say_hello()
    say_goodbye()
复制代码

这样处理好多了,但是呢,还是有问题,因为每个业务函数都需要调用一下 debug() 函数,而且如果以后说某个函数不能使用 debug 函数,岂不是gg了?

这时候,就需要装饰器了;

装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象;有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用;
复制代码

如何使用装饰器

原始版本

def debug(func):
    def wrapper():
        print "[DEBUG]: enter {}()".format(func.__name__)
        return func()
    return wrapper

@debug
def say_hello():
    print "hello!"
复制代码

这是最简单的装饰器,但是有一个问题,如果被装饰的函数需要传入参数,那么这个装饰器就坏了。因为返回的函数并不能接受参数,可以指定装饰器函数wrapper接受和原函数一样的参数,比如:

def debug(func):
    def wrapper(something):  # 指定一毛一样的参数
        print "[DEBUG]: enter {}()".format(func.__name__)
        return func(something)
    return wrapper  # 返回包装过函数

@debug
def say(something):
    print "hello {}!".format(something)
复制代码

这样就解决了一个问题,但又多了N个问题;

因为函数有千千万,只管自己的函数,别人的函数参数是什么样子,鬼知道?还好Python提供了可变参数 *args 和关键字参数 **kwargs ,有了这两个参数,装饰器就可以用于任意目标函数了;

def debug(func):
    def wrapper(*args, **kwargs):  # 指定宇宙无敌参数
        print "[DEBUG]: enter {}()".format(func.__name__)
        print 'Prepare and say...',
        return func(*args, **kwargs)
    return wrapper  # 返回

@debug
def say(something):
    print "hello {}!".format(something)
    
# @debug的意思是,执行debug函数,而传入的参数就是下方紧接的sya函数;
复制代码

小结

1)装饰器的作用就是 为已经存在的函数或对象添加额外的功能

2)闭包函数的必要条件:

  • 闭包函数必须返回一个函数对象
  • 闭包函数返回的那个函数必须引用外部变量(一般不能是全局变量)

3)生成器是一个返回迭代器的函数,只能用于迭代操作,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行;


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

鼠标宣言

鼠标宣言

约翰·里德尔 / 倪萍、梅清豪 / 上海人民 / 2005-08-01 / 25.00

本书针对信息时代营销者不知该如何满足消费者的营销困境,提出了崭新的解决方案——以新技术为基础的群体筛选和推荐系统。随着信息管理软件和internet的高速发展,群体筛选技术下的推荐系统通过大量有关消费者偏好和购物记录的信息,以及对产品特征的准确把握,能够为消费者进行精确的推荐,提高了消费者的购物效率和准确度以及营销者的营销效率和竞争力。本书通过通俗而到位的讲解,向读者全面介绍了有关群体筛选技术的理......一起来看看 《鼠标宣言》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

随机密码生成器
随机密码生成器

多种字符组合密码