• Python高级_第1章_Python闭包装饰器


    Python高级_第1章_Python闭包装饰器+装饰器+PyMySQL

    Python闭包装饰器

    一、闭包

    1、作用域

    在Python代码中,作用域分为两种情况:全局作用域 与 局部作用域

    2、变量的作用域

    在全局定义的变量 => 全局变量

    在局部定义的变量 => 局部变量

    3、全局变量与局部变量的访问范围

    在这里插入图片描述

    ① 在全局作用域中可以访问全局变量,在局部作用域中可以访问局部变量

    # 全局作用域(全局变量)
    num1 = 10
    def func():
        # 局部作用域(局部变量)
        num2 = 20
        # ① 在局部访问局部变量
        print(num2)
    
    # ① 在全局访问全局变量
    print(num1)
    # 调用函数
    func()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    输出结果:

    10
    20
    
    • 1
    • 2

    ② 在局部作用域中可以访问全局变量

    # 全局作用域(全局变量)
    num1 = 10
    def func():
        # 局部作用域(局部变量)
        # ② 在局部作用域中可以访问全局变量
        print(num1)
    
    # 调用函数
    func()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    输出结果:

    10
    
    • 1

    ③ 在全局作用域中不能访问局部变量

    # 全局作用域(全局变量)
    num1 = 10
    def func():
        # 局部作用域(局部变量)
        num2 = 20
    
    # 调用函数
    func()
    # 在全局作用域中调用局部变量num2
    print(num2)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    运行结果:报错

    在这里插入图片描述

    4、问题:为什么在全局作用域中无法访问局部变量

    答:主要原因在于,在Python的底层存在一个“垃圾回收机制”,主要的作用就是回收内存空间。加快计算机的运行。我们在Python代码中定义的变量也是需要占用内存的,所以Python为了回收已经被已经过的内存,会自动将函数运行以后的内部变量和程序直接回收。

    5、问题:我们有没有办法把函数内部的局部变量保留

    在这里插入图片描述

    答:使用闭包

    函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包。

    6、闭包的构成条件(三步走)

    在这里插入图片描述

    第一步:有嵌套

    第二步:有引用

    第三步:有返回(return)
    在这里插入图片描述

    '''
    闭包程序三步走:① 有嵌套 ② 有引用 ③ 有返回
    '''
    
    def func():
        num = 20  # 局部变量
        def inner():
            print(num)
        return inner  # 实际上inner函数并没有执行,只是返回了inner函数在内存中的地址
    
    f = func()  # 相当于把inner在内存中的地址0x7fbc9b3f8e18赋值给变量f
    f()  # 找到inner函数的内存地址,并执行器内部的代码(num=20),在于闭包函数保留了num=20这个局部变量
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    输出结果:

    20
    
    • 1

    闭包的作用:正常情况下,当执行func()的时候,函数内部的变量num = 20,会随着函数的func函数的结束而被垃圾回收机制所回收。所以闭包的真正作用:就是可以在全局作用域中,实现间接对局部变量进行访问。
    在这里插入图片描述
    在这里插入图片描述

    7、注意事项

    注意点:

    由于闭包引用了外部函数的变量,则外部函数的变量没有及时释放,消耗内存。

    8、在闭包的内部实现对外部变量的修改

    错误版本:

    '''
    Python闭包:① 有嵌套 ② 有引用 ③ 有返回
    '''
    
    def outer():
        num = 10
        def inner():
            # 这种写法无法实现通过闭包修改外部的局部变量
            num = 20
        print('outer函数中的num:', num)  # 10
        inner()  # 执行函数inner,让num=20生效
        print('outer函数中的num:', num)  # 10
        return inner
    
    f = outer()
    f()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    正确版本:

    新知识点:nonlocal关键字(在函数内部修改函数外部的变量,这个变量非全局变量)

    老知识点:global关键字(在函数内部声明变量,代表引用全局作用域中的全局变量)

    '''
    Python闭包:① 有嵌套 ② 有引用 ③ 有返回
    '''
    
    def outer():
        num = 10
        def inner():
            # 这种写法无法实现通过闭包修改外部的局部变量'
            nonlocal num
            num = 20
        print('outer函数中的num:', num)  # 输出结果 10
        inner()  # 执行函数inner,让num=20生效
        print('outer函数中的num:', num)  # 输出结果 20
        return inner
    
    f = outer()
    f()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    9、闭包的综合案例

    闭包的作用:可以在全局作用域中间接访问局部变量(在函数执行以后)

    '''
    闭包编写三步走:① 有嵌套 ② 有引用 ③ 有返回
    分析:
    执行f = func()的时候,result赋值为0,然后定义inner,返回inner,最终结果f = inner函数的内存地址
    执行f(1),相当于执行inner函数,nonlocal引用局部变量result=0,然后进行+1操作,弹出0+1=1
    继续执行
    执行f(2),相当于执行inner函数,声明nonlocal result,代表还是引用外部的局部变量,由于此时外部的result已经被
    f(1)更改为1了,所以由于局部变量一直没有消失,所以此时result=1,执行+2操作,最终结果为3
    '''
    def func():
        result = 0
        def inner(num):
            nonlocal result
            result += num
            print(result)
        return inner
    
    f = func()
    f(1)  # 1
    f(2)  # 3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    二、装饰器

    1、什么是装饰器

    在不改变现有函数源代码以及函数调用方式的前提下,实现给函数增加额外的功能。

    装饰器的本质就是一个闭包函数(三步:① 有嵌套 ② 有引用 ③ 有返回)

    有返回代表外部函数返回内部函数的内存地址(内部函数的名称),不带

    2、装饰器的雏形

    # 要求:把登录功能封装起来(比如封装成一个函数,添加这个登录不能影响现有功能函数)
    '''
    装饰器:本质是一个闭包,有嵌套、有引用、有返回(返回的是函数的内存地址)
    参数fn在check中也是一个局部变量
    参数fn:就是要装饰的函数的函数名,如comment,如download
    '''
    def check(fn):
        def inner():
            # 开发登录功能
            print('登录功能')
            # 调用原函数
            fn()
        return inner
    
    
    # 评论功能(前提:登录)
    def comment():
        print('评论功能')
    
    comment = check(comment)
    comment()
    
    # 下载功能(前提:登录)
    def download():
        print('下载功能')
    
    download = check(download)
    download()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    3、装饰器定义

    '''
    装饰器:本质就是一个闭包 ① 有嵌套 ② 有引用 ③ 有返回
    '''
    def check(fn):
        
        def inner():
            # 开发登录验证功能
            print('验证登录')
            # 执行原有函数
            fn()
        return inner
    
    @check
    def comment():
        print('发表评论')
    
    comment()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4、装饰器的作用:获取程序的执行时间

    '''
    定义获取程序的执行时间装饰器 => 闭包(① 有嵌套 ② 有引用 ③ 有返回)
    '''
    import time
    
    def get_time(fn):
        def inner():
            # ① 添加装饰器修饰功能(获取程序的执行时间)
            begin = time.time()
            # ② 调用fn函数,执行原函数代码
            fn()
            end = time.time()
            print(f'这个函数的执行时间:{end - begin}')
        return inner
    
    
    @get_time
    def demo():
        for i in range(1000000):
            print(i)
    
    demo()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    5、带有参数装饰器

    '''
    带有参数的装饰器:① 有嵌套 ② 有引用 ③ 有返回
    '''
    def logging(fn):
        def inner(*args, **kwargs):
            # 添加装饰器代码(输出日志信息)
            print('-- 日志信息:正在努力计算机 --')
            # 执行要修饰的函数
            fn(*args, **kwargs)  # sum_num(a, b)
        return inner
    
    @logging
    def sum_num(*args, **kwargs):
        result = 0
        # *args代表不定长元组参数,args = (10, 20)
        for i in args:
            result += i
        # **kwargs代表不定长字典参数, kwargs = {a:30, b:40}
        for i in kwargs.values():
            result += i
        print(result)
    
    # sum_num带4个参数,而且类型不同,10和20以元组形式传递,a=30,b=40以字典形式传递
    sum_num(10, 20, a=30, b=40)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    6、带有返回装饰器

    '''
    带有返回值的装饰器:① 有嵌套 ② 有引用 ③ 有返回
    如果一个函数执行完毕后,没有return返回值,则默认返回None
    '''
    def logging(fn):
        def inner(*args, **kwargs):
            print('-- 日志信息:正在努力计算 --')
            return fn(*args, **kwargs)  # fn() = sub_num(20, 10) = result
        return inner
    
    @logging
    def sub_num(a, b):
        result = a - b
        return result
    
    print(sub_num(20, 10))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    7、通用版本的装饰器(以后所有的装饰器以此为准)

    '''
    通用装饰器:① 有嵌套 ② 有引用 ③ 有返回 ④ 有不定长参数 ⑤ 有return返回值
    '''
    def logging(fn):
        def inner(*args, **kwargs):
            # 输出装饰器功能
            print('-- 正在努力计算 --')
            # 调用fn函数
            return fn(*args, **kwargs)
        return inner
    
    
    @logging
    def sum_num1(a, b):
        result = a + b
        return result
    
    print(sum_num1(20, 10))
    
    @logging
    def sum_num2(a, b, c):
        result = a + b + c
        return result
    
    print(sum_num2(10, 20, 30))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    8、装饰器高级:使用装饰器传递参数(了解)

    基本语法:

    def 装饰器(fn):
        ...
    
    @装饰器('参数')
    def 函数():
        # 函数代码
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    实例代码:根据传递参数不同,打印不同的日志信息

    '''
    通用装饰器:① 有嵌套 ② 有引用 ③ 有返回 ④ 有不定长参数 ⑤ 有return返回值
    真正问题:通过装饰器传递参数,我们应该如何接收这个参数呢?
    答:在logging方法的外侧在添加一个函数,专门用于接收传递过来的参数
    '''
    
    def logging(flag):
        # flag = + 或 flag = -
        def decorator(fn):
            def inner(*args, **kwargs):
                if flag == '+':
                    print('-- 日志信息:正在努力进行加法运算 --')
                elif flag == '-':
                    print('-- 日志信息:正在努力进行减法运算 --')
                return fn(*args, **kwargs)
            return inner
        return decorator
    
    @logging('+')
    def sum_num(a, b):
        result = a + b
        return result
    
    @logging('-')
    def sub_num(a, b):
        result = a - b
        return result
    
    
    print(sum_num(10, 20))
    print(sub_num(100, 80))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    9、扩展:类装饰器(了解)

    装饰器还有一种特殊的用法就是类装饰器,就是通过定义一个类来装饰函数。

    class 类装饰器():
        # 装饰器代码
    
    @类装饰器名称
    def 函数():
        # 函数代码
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    举个栗子:编写一个Check类装饰器,用于实现用户的权限验证

    '''
    类装饰器编写规则:
    ① 必须有一个__init__初始化方法,用于接收要装饰函数的函数 
    ② 必须把这个类转换为可以调用的函数
    问题:如何把一个类当做一个装饰器函数进行调用(把类当做函数)
    '''
    
    class Check():
        def __init__(self, fn):
            # fn就是要修饰函数的名称,当Check装饰器类被调用时,系统会自动把comment函数名称传递给fn变量
            self.__fn = fn
        # __call__方法:把一个类转换为函数的形式进行调用
        def __call__(self, *args, **kwargs):
            # 编写装饰器代码
            print('请先登录')
            # 调用comment函数本身
            self.__fn(*args, **kwargs)
    
    # 编写一个函数,用于实现评论功能,底层comment = Check(comment)
    @Check
    def comment():
        print('评论功能')
    
    # 调用comment函数,实现评论功能
    comment()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    @Check 等价于 comment = Check(comment), 所以需要提供一个init方法,并多增加一个fn参数。

    要想类的实例对象能够像函数一样调用,需要在类里面使用call方法,把类的实例变成可调用对象(callable),也就是说可以像调用函数一样进行调用。

    call方法里进行对fn函数的装饰,可以添加额外的功能。

    目标:① 了解闭包的作用以及闭包的基本语法(三步走)

    ​ ② 能独立完成通用装饰器的编写

    ​ ③ 能使用装饰器传递参数

    三、PyMySQL

    前提

    提前安装MySQL数据库(可以使用Linux系统的,也可以使用Windows版本,如小皮面板)

    1、为什么要学习PyMySQL

    如何实现将100000条数据插入到MySQL数据库?

    答案:

    如果使用之前学习的MySQL客户端来完成这个操作,那么这个工作量无疑是巨大的,我们可以通过使用程序代码的方式去连接MySQL数据库,然后对MySQL数据库进行增删改查的方式,实现100000条数据的插入,像这样使用代码的方式操作数据库就称为数据库编程。

    2、安装PyMySQL模块

    安装PyMySQL:

    # pip install pymysql
    
    • 1

    卸载PyMySQL:

    # pip uninstall pymysql
    
    • 1

    3、PyMySQL的使用(七步走)

    ☆ 导入 pymysql 包

     import pymysql
    
    • 1

    ☆ 创建连接对象

    调用pymysql模块中的connect()函数来创建连接对象,代码如下:

     conn=connect(参数列表)
    
     * 参数host:连接的mysql主机,如果本机是'localhost'
     * 参数port:连接的mysql主机的端口,默认是3306
     * 参数user:连接的用户名
     * 参数password:连接的密码
     * 参数database:数据库的名称
     * 参数charset:通信采用的编码方式,推荐使用utf8
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    连接对象操作说明:

    • 关闭连接 conn.close()
    • 提交数据 conn.commit()
    • 撤销数据 conn.rollback()

    ☆ 获取游标对象

    获取游标对象的目标就是要执行sql语句,完成对数据库的增、删、改、查操作。代码如下:

     # 调用连接对象的cursor()方法获取游标对象   
     cur =conn.cursor()
    
    • 1
    • 2

    游标操作说明:

    • 使用游标执行SQL语句: execute(operation [parameters ]) 执行SQL语句,返回受影响的行数,主要用于执行insert、update、delete、select等语句
    • 获取查询结果集中的一条数据:cur.fetchone()返回一个元组, 如 (1,‘张三’)
    • 获取查询结果集中的所有数据: cur.fetchall()返回一个元组,如((1,‘张三’),(2,‘李四’))
    • 关闭游标: cur.close(),表示和数据库操作完成

    ☆ pymysql完成数据的查询操作

    import pymysql
    
    # 创建连接对象
    conn = pymysql.connect(host='localhost', port=3306, user='root', password='mysql',database='db_itheima', charset='utf8')
    
    # 获取游标对象
    cursor = conn.cursor()
    
    # 查询 SQL 语句
    sql = "select * from students;"
    # 执行 SQL 语句 返回值就是 SQL 语句在执行过程中影响的行数
    row_count = cursor.execute(sql)
    print("SQL 语句执行影响的行数%d" % row_count)
    
    # 取出结果集中一行数据, 例如:(1, '张三')
    # print(cursor.fetchone())
    
    # 取出结果集中的所有数据, 例如:((1, '张三'), (2, '李四'), (3, '王五'))
    for line in cursor.fetchall():
        print(line)
    
    # 关闭游标
    cursor.close()
    
    # 关闭连接
    conn.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    ☆ pymysql完成对数据的增删改

    import pymysql
    
    # 创建连接对象
    conn = pymysql.connect(host='localhost', port=3306, user='root', password='mysql',database='db_itheima', charset='utf8')
    
    # 获取游标对象
    cursor = conn.cursor()
    
    try:
        # 添加 SQL 语句
        # sql = "insert into students(name) values('刘璐'), ('王美丽');"
        # 删除 SQ L语句
        # sql = "delete from students where id = 5;"
        # 修改 SQL 语句
        sql = "update students set name = '王铁蛋' where id = 6;"
        # 执行 SQL 语句
        row_count = cursor.execute(sql)
        print("SQL 语句执行影响的行数%d" % row_count)
        # 提交数据到数据库
        conn.commit()
    except Exception as e:
        # 回滚数据, 即撤销刚刚的SQL语句操作
        conn.rollback()
    
    # 关闭游标
    cursor.close()
    
    # 关闭连接
    conn.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    说明:

    • conn.commit() 表示将修改操作提交到数据库
    • conn.rollback() 表示回滚数据

    ☆ pymysql实现安全连接操作(扩展)

    from pymysql import connect
    
    def main():
    
        find_name = input("请输入物品名称:")
    
        # 创建Connection连接
        conn = connect(host='localhost',port=3306,user='root',password='mysql',database='db_itheima',charset='utf8')
        # 获得Cursor对象
        cs1 = conn.cursor()
    
        # 非安全的方式
        # 输入 ' or 1 = 1 or '   (单引号也要输入)
        # sql = "select * from goods where name='%s'" % find_name
        # print("""sql===>%s<====""" % sql)
        # # 执行select语句,并返回受影响的行数:查询所有数据
        # count = cs1.execute(sql)
    
        # 安全的方式
        # 构造参数列表
        params = [find_name]
        # 执行select语句,并返回受影响的行数:查询所有数据
        count = cs1.execute("select * from goods where name=%s", params)
        # 注意:
        # 如果要是有多个参数,需要进行参数化
        # 那么params = [数值1, 数值2....],此时sql语句中有多个%s即可
        # %s 不需要带引号
    
        # 打印受影响的行数
        print(count)
        # 获取查询的结果
        # result = cs1.fetchone()
        result = cs1.fetchall()
        # 打印查询的结果
        print(result)
        # 关闭Cursor对象
        cs1.close()
        # 关闭Connection对象
        conn.close()
    
    if __name__ == '__main__':
        main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    _name
    # print(“”“sql===>%s<====”“” % sql)
    # # 执行select语句,并返回受影响的行数:查询所有数据
    # count = cs1.execute(sql)

    # 安全的方式
    # 构造参数列表
    params = [find_name]
    # 执行select语句,并返回受影响的行数:查询所有数据
    count = cs1.execute("select * from goods where name=%s", params)
    # 注意:
    # 如果要是有多个参数,需要进行参数化
    # 那么params = [数值1, 数值2....],此时sql语句中有多个%s即可
    # %s 不需要带引号
    
    # 打印受影响的行数
    print(count)
    # 获取查询的结果
    # result = cs1.fetchone()
    result = cs1.fetchall()
    # 打印查询的结果
    print(result)
    # 关闭Cursor对象
    cs1.close()
    # 关闭Connection对象
    conn.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    if name == ‘main’:
    main()

    
    
    
    
    • 1
    • 2
    • 3
  • 相关阅读:
    编辑器插件
    15:00面试,15:08就出来了,问的问题有点变态。。。
    嵌软工程师要掌握的硬件知识1:一文了解什么是PN结
    11-包装类
    css的三种引入方式
    Adobe ME下载、Media Encoder下载
    微信小程序搜索局域网设备
    Cannot find module ‘./assets/empty-module.js‘
    winedt常用快捷键 修改快捷键latex编译按钮
    Blazor快速开发框架Known-V2.0.0
  • 原文地址:https://blog.csdn.net/guan1843036360/article/details/127579197