Python基础-闭包和装饰器

目的

为避免一学就会、一用就废,这里做下笔记

一、闭包

1、闭包的定义

闭包是编程语言的一种特殊机制,属于高阶函数的一种,具体是:

1.在函数嵌套(函数里面再定义函数)的前提下

2.内部函数使用了外部函数的变量(或外部函数的参数)

3.外部函数返回了内部函数

2、样例代码

python 复制代码
# 外部函数返回内部函数,内部函数使用外部函数的变量/入参
def outer_func(num1):
    # num1为入参
    outer_num=num1

    def inner_func(num2):
        # 通过nonlocal关键字,声明变量为外部函数的变量
        nonlocal outer_num
        # 修改外部函数变量
        outer_num+=1
        print(f'inner func return {outer_num+num2}')
        return outer_num+num2

    print(outer_num)
    inner_func(3)
    print(outer_num)

    return inner_func

if __name__ == '__main__':
    f=outer_func(2) # 返回内部函数,先打印2,然后outer_num变成3,打印inner func...,再打印3
    print(f)    # f是一个函数对象
    print(f(4)) # 原outer_num=3,然后outer_num+=1变成4,打印inner func return 8,再打印8

3、闭包的优点

1、可实现装饰器模式

2、可实现函数工厂模式

3、可实现函数模块化与复用

4、闭包的缺点

1、提高代码复杂度

2、调试困难

3、容易内存泄漏

二、装饰器

1、装饰器的定义

给已有函数增加额外功能的函数,它本质上是一个闭包函数。

装饰器的功能特点:

  • 不修改已有函数的源代码
  • 不修改已有函数的调用方式
  • 给已有函数增加额外的功能

装饰器的使用:

Python给提供了一个装饰函数更加简单的用法:@装饰器名字;可以完成对已有函数的装饰。

python 复制代码
# 不修改已有函数的代码和调用方式
# 给已有的函数增加额外的功能

def login(func):    # 参数必须是一个函数
    print('装饰器开始执行')

    def inner_func():
        print('检查是否登录,若没有则登录')
        func()

    return inner_func

# 需要先登录,才能发表评论
def comment():
    print('发表评论')

@login  # 使用@语法,标识它使用login对comment函数进行装饰,类似java中的注解
def comment2():
    print('发表评论2')

if __name__ == '__main__':
    # 方法1:直接使用,缺点是多写一行嵌套的代码
    # 给comment()方法添加login装饰器,返回inner_func
    comment = login(comment)
    # 执行inner_func,先打印:检查是否登录,若没有则登录,后打印:发表评论
    comment()
    # 方法2:通过@使用装饰器,其他代码不变
    comment2()

注意:@check 等价于 comment = check(comment)

2、使用案例

给多个函数,增加一个:输出函数执行时间的功能

python 复制代码
# 给多个函数增加打印执行时间的功能
import time

def timer(func):
    def inner_func():
        start = time.time()
        func()
        end = time.time()
        print(f'函数{func.__name__}的执行耗时{end-start}秒')

    return inner_func

@timer
def test1():
    print('test1')
    time.sleep(1)

def test2():
    print('test2')
    time.sleep(2)

@timer
def test3():
    print('test3')
    time.sleep(2)

if __name__ == '__main__':
    test1()
    test2()
    test3()

"""
执行结果如下:
test1
函数test1的执行耗时1.001053810119629秒
test2
test3
函数test3的执行耗时2.000431537628174秒
"""

3、带参数的装饰器

带有参数的装饰器就是使用装饰器装饰函数的时候可以传入指定参数,语法格式: @装饰器(参数,...)

  • 装饰器只能接收一个参数,并且还是函数类型。
  • 在装饰器外面再包裹上一个函数,让最外面的函数接收参数,返回的是装饰器。

案例需求:编写装饰器,为多个函数加上认证功能(用户清单来源于文件),要求登录成功一次,后续的函数都无需再输入用户名和密码,直接可以执行。

python 复制代码
# 带参数的装饰器
# 装饰器的基础上,再包裹一层函数,返回装饰器(3阶函数)
from functools import wraps

# 用户数据文件,内容如下:
#     {'name':'张三','passwd':'123456'},
#     {'name':'李四','passwd':'123456'},
file_path='../resource/users.data'

# 是否已经登录
already_login = False

def login(file_path):
    # 初始化用户信息,读取文件中的用户(该文件会被多次加载,次数=@login()的次数)
    user_list = []
    with open(file_path,'r',encoding='utf-8') as f:
        for line in f.readlines():
            user_list.append(eval(line))
        print('加载用户列表成功')   # 该行会被打印三次。若要只加载1次,可考虑将user_list和加载过程变成全局变量

    def login_decorator(func):

        @wraps(func)   # 使用内置的wraps装饰器对func做伪装,使得原func函数不要在被装饰后丢失身份信息
        def inner_func(*args, **kwargs):
            global already_login
            # 判断是否已登录,若已登录,则跳过验证;否则进行登录.
            # 因为要全局生效,该变量需要定义在login外
            if not already_login:
                print(f'用户打算执行{func.__name__},但未登录,请先登录')
                name=input('用户名:\t')
                passwd=input('密码:\t')
                for user in user_list:
                    if user['name'] == name and user['passwd'] == passwd:
                        print('用户登录成功,执行函数')
                        func(*args, **kwargs)
                        # 修改全局变量,标识已登录
                        already_login = True
                        break
                else:
                    print(f'账号密码错误,不执行{func.__name__}')
            else:
                print('用户已登录,直接执行')
                func(*args, **kwargs)
        return inner_func

    return login_decorator

@login(file_path)
def test1():
    print('test1')

@login(file_path)
def test2():
    print('test2')

@login(file_path)
def test3():
    print('test3')

if __name__ == '__main__':
    print(f'test1函数的名字是:{test1.__name__}') # 验证wraps伪装是否生效
    test1()
    test2()
    test3()

4、装饰器类的写法

装饰器的另一种实现方式:通过定义一个类来装饰函数。需要在类里面使用call函数 ,把类的实例变成可调用对象callable

python 复制代码
# 装饰器类,重写__call__方法,里面实现inner函数的内容
class Login:

    def __init__(self,func):
        self.func = func

    # 如果是装饰器类,必须重写call函数
    def __call__(self,*args,**kwargs):
        print('检查是否登录,若没有则登录,则先登录再说')
        self.func(*args,**kwargs)

@Login
def test1():
    print("test1")

if __name__ == '__main__':
    print(test1.__class__.__name__)
    test1()

5、property装饰器

property装饰器是一个python内置装饰器,该装饰器可把一个方法当做属性进行使用,以便调用时简化代码。

python 复制代码
class Person:

    def __init__(self):
        self.__age = 0

    # 获取年龄
    @property
    def age(self):
        return self.__age

    # 该装饰器,把age当成属性,用于给age赋值
    @age.setter
    def age(self,new_age):
        if new_age>=200 or new_age<=0:
            raise Exception('年龄非法')
        else:
            self.__age = new_age


if __name__ == '__main__':
    p = Person()
    # 像普通属性一样操作私有属性
    p.age=10
    print(p.age)

6、property装饰器的实现原理

python 复制代码
# property实际上是一个描述符类
class MyProperty:
    """简化版的property实现"""
    def __init__(self, fget=None, fset=None, fdel=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
    
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("不可读")
        return self.fget(obj)
    
    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("不可写")
        self.fset(obj, value)
    
    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("不可删除")
        self.fdel(obj)
    
    def setter(self, fset):
        """创建新的property对象,设置新的setter"""
        return type(self)(self.fget, fset, self.fdel)

# 使用自定义的property
class Demo:
    def __init__(self, x):
        self._x = x
    
    def get_x(self):
        return self._x
    
    def set_x(self, value):
        self._x = value
    
    x = MyProperty(get_x, set_x)

demo = Demo(10)
print(demo.x)  # 10
demo.x = 20    # 调用set_x
print(demo.x)  # 20
相关推荐
三维空间1 小时前
如何在Python多进程中避免死锁问题?
python
冤大头编程之路1 小时前
Python并发编程实操教程:多线程/多进程/异步全解析
python
dhdjjsjs1 小时前
Day30 Python Study
开发语言·前端·python
十五年专注C++开发1 小时前
async_simple:一个轻量级C++异步协程框架
开发语言·网络·c++·boost·asio
Eric.Lee20212 小时前
mujoco构建无物理约束的几何体运动
python·物理引擎·mujoco·物理模型仿真
难以触及的高度2 小时前
Java for循环完全指南:从基础到高性能实践
java·开发语言
wadesir2 小时前
用Python实现ggplot2风格绘图(零基础入门Seaborn与Matplotlib美化技巧)
开发语言·python·matplotlib
油炸自行车2 小时前
【Qt】Qt Creator Debug模式提示“缺少 Windows CDB 调试器配套的扩展组件“”
开发语言·windows·qt
budingxiaomoli2 小时前
多线程(三)
java·开发语言