python闭包与装饰器(语法糖)の拾遗

1、闭包

1.1 何谓闭包

在维基百科里提到闭包的概念

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是在支持头等函数的编程语言中实现词法绑定的一种技术。闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。环境里是若干对符号和值的对应关系,它既要包括约束变量(该函数内部绑定的符号),也要包括自由变量(在函数外部定义但在函数内被引用),有些函数也可能没有自由变量。闭包跟函数最大的不同在于,当捕捉闭包的时候,它的自由变量会在捕捉时被确定,这样即便脱离了捕捉时的上下文,它也能照常运行。捕捉时对于值的处理可以是值拷贝,也可以是名称引用,这通常由语言设计者决定,也可能由用户自行指定(如C++)。
闭包的概念出现于60年代,最早实现闭包的程序语言是Scheme。之后,闭包被广泛使用于函数式编程语言如ML语言和LISP。很多命令式程序语言也开始支持闭包。

Alex描述的闭包

关于闭包,即函数定义和函数表达式位于另一个函数的函数体内(嵌套函数)。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。也就是说,内部函数会在外部函数返回后被执行。而当这个内部函数执行时,它仍然必需访问其外部函数的局部变量、参数以及其他内部函数。这些局部变量、参数和函数声明(最初时)的值是外部函数返回时的值,但也会受到内部函数的影响。

1.2 闭包函数

闭包函数 —— 嵌套函数,内部的函数引用了外部函数的变量

1.2.1 作用域是个啥??

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

  • 函数也是如此,比如函数func()中,再定义函数inner()
    如下这样的,你觉得直接使用func(),会输出打印 a 吗???
    def func():
        a = 10
        def inner():
            print(a)
    
    func()
    
    这样肯定不会输出 a 的,因为inner()函数根本没有执行
    在这里插入图片描述
  • 给外部函数加一个返回值,通过返回值来执行内部函数不就好啦!
    最终效果:下边的 f() 相当于 执行inner()
    在这里插入图片描述
    注意此时func已经执行完毕,正常情况下func里的内存都已经释放了,但此时由于闭包的存在,我们却还可以调用inner, 并且inner内部还调用了上一层func里的 a 变量。这种粘粘糊糊的现象就是闭包。

闭包的意义:返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域

1.2.2 闭包函数定义

内部函数包含对外部作用域而非全剧作用域名字的引用,该内部函数称为闭包函数

函数内部定义的函数称为内部函数

闭包函数,本质上就是一个嵌套函数,内部的函数引用了外部函数的变量、或参数。比如在函数func()定义了又定义了一个函数inner(),而在函数inner()内引用相对外函数func()的一些变量啊、参数啊,这种称之为闭包函数。

  • 判断闭包函数的方法__closure__
#输出的__closure__有cell元素 :是闭包函数
def func():
    a = 10
    def inner():
        print(a)
    print(inner.__closure__)
    return inner

f = func()
f()

这个inner()就是闭包函数,因并引用了外部函数func作用域内的变量、参数
在这里插入图片描述

# 输出的__closure__为None :不是闭包函数
a = 10
def func():
    def inner():
        print(a)
    print(inner.__closure__)
    return inner

f = func()
f()

这个inner()则不是闭包函数,因并未引用外部函数func作用域内的变量、参数在这里插入图片描述
这个inner()也不是闭包函数,因并未引用外部函数func作用域内的变量、参数
在这里插入图片描述

2、装饰器

装饰器(Decorators)是 Python 的一个重要部分,简单地说:他们是修改其他函数的功能的函数。他们有助于让我们的代码更简短

2.1 装饰器基本结构

  • python语法 提供的 语法糖
  • 保证在不改变原有的函数的调用方式的情况下
  • 在函数执行之前或者之后要完成某个固定的动作

装饰器的本质:一个闭包函数
装饰器的功能:在不修改原函数及其调用方式的情况下对原函数功能进行扩展

装饰器通用模板

from functools import wraps

def wrapper(func):
    @wraps(func)        # 加在最内层函数上方 ,保证查看函数名或函数注释的等方式仍然生效
    def inner(*args, **kwargs):
        '''执行函数之前要做的'''
        res = func(*args, **kwargs)
        '''执行函数之后要做的'''
        return res
    return inner

调用装饰器,语法糖

@wrapper
def demo():
    """
    this is test.........!!!!!!
    :return:
    """
    print('---> from demo()')

print(demo.__name__)
print(demo.__doc__)

执行结果如下:

demo

    this is test.........!!!!!!
    :return:

2.3 eva 对装饰器的总结

开放封闭原则

1. 对扩展是开放的

为什么要对扩展开放呢?

我们说,任何一个程序,不可能在设计之初就已经想好了所有的功能并且未来不做任何更新和修改。所以我们必须允许代码扩展、添加新功能。

2. 对修改是封闭的

为什么要对修改封闭呢?

就像我们刚刚提到的,因为我们写的一个函数,很有可能已经交付给其他人使用了,如果这个时候我们对其进行了修改,很有可能影响其他已经在使用该函数的用户。

装饰器完美的遵循了这个开放封闭原则。

def 装饰器名(func):
    def inner(*args,**kwargs):
        # 判断是否登录、判断是否有权限执行
        ret =func(*args,**kwargs)    # 被装饰的函数
        # 记录结果、日志
        return ret
    return inner

2.4 带参数的装饰器

比普通的装饰器再多加一层函数用于接收并传参,注意每一层的返回值

  • 通用模板,举例
# 当flag为 True时,再执行 timer装饰器
def outer(flag):
    def timer(func):
        def inner(*args,**kwargs):
            if flag:
                print('''执行函数之前要做的''')
            re = func(*args,**kwargs)
            if flag:
                print('''执行函数之后要做的''')
            return re
        return inner
    return timer

@outer(False)
def func():
    print(111)

func()

以用户名为参数来执行log装饰器

def log_required(username):
    def decor(func):
        def inner(*args, **kwargs):
            print(f'用户名:{username}, 执行了{func.__name__}')
            res = func(*args, **kwargs)
            return res
        return inner
    return decor

@log_required('bobo')
def article_page():
    print('this is article page')
    return True

article_page()

执行结果

用户名:bobo, 执行了article_page
this is article page

2.5 一个函数使用多个装饰器

一个函数可以被多个装饰器进行装饰

import time
from functools import wraps

def timmer(func):
    def inner(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        cost_time = round(time.time() - start_time, 2)
        print(f'本次程序共花费{cost_time}s')
        return res
    return inner

def log_required(func):
    @wraps(func)
    def inner(*args, **kwargs):
        print(f'{func.__name__}被执行了一次!')
        res = func(*args, **kwargs)
        return res
    return inner
    
@timmer
@log_required
def func1():
    li = [x for x in range(1000000) if x % 3 is 0]
    return li

2.6 猜一猜执行结果哦!

复用多个装饰器
@dec1
@dec2
实际上是嵌套装饰,即 dec1(dec2(test))
执行顺序也是由内而外执行,从附加前条件执行结束到 加载函数func执行,最后再是附加后条件执行

看到这篇博客,执行示例还不错
https://blog.csdn.net/xiangxianghehe/article/details/77170585?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param

def dec1(func):  
    print("1111")  
    def one():  
        print("2222")  
        func()  
        print("3333")  
    return one  
  
def dec2(func):  
    print("aaaa")  
    def two():  
        print("bbbb")  
        func()  
        print("cccc")  
    return two  
 
@dec1  
@dec2  
def test():  
    print("test test")  
  
test()  # dec1(dec2(test))

输出:

aaaa  
1111  
2222  
bbbb  
test test  
cccc  
3333

装饰器的外函数和内函数之间的语句是没有装饰到目标函数上的,而是在装载装饰器时的附加操作。 17~20行是装载装饰器的过程,相当于执行了test=dec1(dec2(test)),所以此时先执行dec2(test),结果是输出aaaa、将func指向函数test、并返回函数two,然后执行dec1(two),结果是输出1111、将func指向函数two、并返回函数one,然后进行赋值,用函数替代了函数名test。 22行则是实际调用被装载的函数,这时实际上执行的是函数one,运行到func()时执行函数two,再运行到func()时执行未修饰的函数test。
在这里插入图片描述

2.7 装饰器练习

1、程序运行时间

import time

def timmer(func):
    def inner(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        cost_time = round(time.time() - start_time, 2)
        print(f'本次程序共花费{cost_time}s')
        return res
    return inner


@timmer
def func1():
    li = [x for x in range(1000000) if x % 3 is 0]
    return li


@timmer
def func2():
    mcase = {'a': 10, 'b': 34}
    mcase_frequecy = {mcase[k]: k for k in mcase}
    return mcase_frequecy


res1 = func1()
res2 = func2()

2、打印日志

from functools import wraps

def log_required(func):
    @wraps(func)
    def inner(*args, **kwargs):
        print(f'{func.__name__}被执行了一次!')
        res = func(*args, **kwargs)
        return res
    return inner

@log_required
def login():
    pass

@log_required
def register():
    pass

login()
register()

执行结果

login被执行了一次!
register被执行了一次!

3、模拟博客园登录

模拟博客园登录:
1),启动程序,首页面应该显示成如下格式:
        欢迎来到博客园首页
            1:请登录
            2:请注册
            3:文章页面
            4:日记页面
            5:评论页面
            6:收藏页面
            7:注销
            8:退出程序
2),用户输入选项,3~6选项必须在用户登录成功之后,才能访问成功。
3),用户选择登录,用户名密码从register文件中读取验证,三次机会,没成功则结束整个程序运行,成功之后,可以选择访问3~6项,访问页面之前,必须要在log文件中打印日志,日志格式为-->用户:xx 在xx年xx月xx日 执行了 %s函数,访问页面时,页面内容为:欢迎xx用户访问评论(文章,日记,收藏)页面
4),如果用户没有注册,则可以选择注册,注册成功之后,可以自动完成登录,然后进入首页选择。
5),注销用户是指注销用户的登录状态,使其在访问任何页面时,必须重新登录。
6),退出程序为结束整个程序运行。

暂时验证用户状态是通过用户输入的,以后会用header或cookie传值获取权限等

import time
import os
is_authenticated = []
menu_info = """
 *****************  欢迎来到博客园首页  ********************
                    1:  请登录
                    2:  请注册
                    3:  文章页面
                    4:  日记页面
                    5:  评论页面
                    6:  收藏页面
                    7:  注销
                    8:  退出程序
"""

# 初始化登录文件并设置默认用户
with open('register.txt', mode='w', encoding='utf-8') as f:
    f.write('admin|admin\n')


def login():
    times = 0
    while times < 3:
        times += 1
        username = input('请输入用户名:')
        password = input('请输入密码:')
        with open('register.txt', mode='r', encoding='utf-8') as f1:
            for line in f1:
                user, pwd = line.strip().split('|')
                if username == user and password == pwd:
                    print('登录成功!')
                    is_authenticated.append(user)
                    return True
                elif username == user and times < 3:
                    print('用户名或密码有误,请重新输入!')
                elif username == user and times >= 3:
                    print('登录次数过多,请稍后重试!')
                    return False


def register():
    username = input('请输入用户名:')
    password = input('请输入密码:')
    flag_register = True
    with open('register.txt', mode='r', encoding='utf-8') as f1,\
        open('register.txt.new', mode='w', encoding='utf-8') as f2:
        for line in f1:
            f2.write(line)
            user, pwd = line.strip().split("|")
            if user == username:
                print('该用户名已存在,请重新输入!')
                flag_register = False
        if flag_register:
            print('注册成功!')
            f2.write(f'{username}|{password}\n')
    os.remove('register.txt')
    os.rename('register.txt.new', 'register.txt')
    return flag_register


def log_required(func):
    def inner(*args, **kwargs):
        username = input('请输入用户名记录日志:')
        with open('cnblogs.log', mode='a', encoding='utf-8') as f:
            time_exe = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
            log_info = '用户:{user}:在{time}执行了{func_name}函数\n'.format(user=username, time=time_exe, func_name=func.__name__)
            f.write(log_info)
        res = func(*args, **kwargs)
        return res
    return inner


def login_required(func):
    def inner(*args, **kwargs):
        username = input('请输入用户名验证登录态:')
        if username not in is_authenticated and not login():
            return False
        else:
            res = func(*args, **kwargs)
            return res

    return inner


@login_required
@log_required
def article_page():
    user = input('请输入用户名访问页面:')
    print('**************************')
    print(f'欢迎{user}用户访问文章页面!')
    print('**************************')


@login_required
@log_required
def diary_page():
    user = input('请输入用户名访问页面:')
    print('**************************')
    print(f'欢迎{user}用户访问日记页面!')
    print('**************************')


@login_required
@log_required
def comment_page():
    user = input('请输入用户名访问页面:')
    print('**************************')
    print(f'欢迎{user}用户访问评论页面!')
    print('**************************')


@login_required
@log_required
def favorite_page():
    user = input('请输入用户名访问页面:')
    print('**************************')
    print(f'欢迎{user}用户访问收藏页面!')
    print('**************************')


def logout():
    username = input('请输入待注销的用户名:')
    if username in is_authenticated:
        is_authenticated.remove(username)
        print(f'{username}用户注销成功!')
    else:
        print('该用户尚未登录!')


while True:
    func_map = {'1': login, '2': register, '3': article_page, '4': diary_page,
                '5': comment_page, '6': favorite_page, '7': logout, '8': exit}
    print(menu_info)
    inp = input('请输入功能选项:')
    if inp in func_map:
        func = func_map[inp]
        ret = func()
    else:
        print('输入有误,请重新输入!')

猜你喜欢

转载自blog.csdn.net/Sunny_Future/article/details/107970291