深入理解闭包,装饰器,深拷贝浅拷贝

❗ 可乐发布文章是为了分享编程语言 python 的魅力,没有在网上发布群号以及广告。
💚 如果感兴趣的话,大家可以关注一下可乐的公众号(结尾处二维码),就是对可乐最大的支持。

本篇内容可乐不仅仅呈现闭包,装饰器以及深拷贝、浅拷贝的用法,还会和大家一起来理解这几个高级用法,以及使用场景。相信大家看完全篇之后不仅仅会用这些高级用法,还知道在哪些地方用,如何用。

闲话不多说,直接上干货吧!

一、闭包

定义:闭包指的是能够读取其他函数内部变量的函数。他的表现形式是定义在函数内部的函数。

看到定义,大家可能觉得一脸懵逼。那么通俗的说就是,内部函数引用了外部函数的局部变量,那么就可以说创建了一个闭包。

在理解闭包之前,先回想一下函数的作用域(如果忘记了,大家可以往回看一下Python基础 - 下篇的函数部分)。

✔ 语法

def 外部函数名(*args,**kwargs):
  外部函数代码块
  def 内部函数名(*args,**kwargs):
    内部函数代码块(注意:这里有使用到外部函数的变量)
  return 内部函数名

✔ 举例1

def out_func():
    name = "可乐"
    def inner_func():
        print(name)
    return inner_func
inner_func = out_func()
inner_func()# 输出: 可乐

✔ 举例2

def out_func():
    name = "可乐"
    print(f"外部函数变量名name的内存地址是{id(name)}")
    def inner_func():
        print(f"内部函数变量名name的内存地址是{id(name)}")return inner_func
​
inner_func = out_func()
inner_func()

结果如下图:

✔ 闭包的作用(功能)

在描述闭包功能之前,先来看看函数的作用域函数的作用域是在该函数内部生效。当函数代码执行结束后,该函数内部的变量就会被解释器的垃圾回收机制(gc 模块)回收。
所以闭包的作用就是:当内部函数使用了外部函数的局部变量后,python 解释器在垃圾回收时会发现内部函数执行需要依赖外部函数的变量,那么解释器就不会回收该变量所占用的资源。

✔ 使用场景

🙋‍♂️:闭包一般在什么地方会用呢?
👨‍💻:根据定义可知,有一些场景需要去访问其他函数的局部变量,那么你可以使用闭包。一般来说闭包会搭配装饰器来使用,单独使用的场景比较少见。

二、装饰器

解释:在不改变原对象的情况下,动态的扩展对象功能,需要遵循开放封闭原则。

✔ 开放封闭原则

何谓开放封闭原则,开放指的是源代码新功能开放封闭指的是源代码修改是不允许的、是封闭的

✔ 无参数语法

@装饰器名
def 函数名(*args,**kwargs):
  实际代码

✔ 有参数语法

@装饰器名(参数)
def 函数名(*args,**kwargs):
  实际代码

✔ 思考

在开始实现装饰器之前,可乐先抛出一个问题:我想打印一下函数 demo1 的执行时间,有什么方法呢 ?

代码如下:

import time
​
def demo1():
    for i in range(100):
        pass
​
before_time = time.time()
demo1()
after_time = time.time()
print(after_time - before_time)

结果解释:

time.time() 返回的是当前时刻的时间戳秒数,所以打印出来的就是该函数的执行时间。

如果可乐还想知道 demo2,demo3,demo4 函数的执行时间,那么是不是要写很多重复的代码呢?这个时候我们可以用装饰器来解决这个问题。

2.1 无参数装饰器

✔ 装饰器打印函数运行时间

import time
​
def print_time(func):
    def wrapper():
        before_time = time.time()
        func()
        after_time = time.time()
        print(after_time - before_time)return wrapper
​
@print_time
def demo1():
    for i in range(100):
        pass
​
demo1()

✔ 详细拆解

如果大家还没有看懂是什么个情况,可乐再给上述的函数拆解一下。

① 我们先定义一个如下函数:

import time
​
def print_time(func):
    def wrapper():
        before_time = time.time()
        func()
        after_time = time.time()
        print(after_time - before_time)return wrapper
​
def demo1():
    for i in range(10):
        print(1)

② 此时如果我们需要打印 demo1 的执行时间,那么只需要这样调用:

wrapper = print_time(demo1)
wrapper()

和上述装饰器计算函数执行时间的具有一样的效果,由此我们可以看出:@print_time 其实就和 print_time(demo1) 是等价的

2.2 有参数装饰器

🙋‍♂️ 根据上面可乐拆解的步骤以及语法,大家思考一下如果是有参数的装饰器那么应该是咋样的呢?以及拆分步骤是如何?

根据上面无参数的代码可知:
 → @print_time 实际等价于 print_time(demo1)
 → 而有参数的语法是:@print_time(参数)
所以我们可以推断出:
 → @print_time(参数) 等价于 print_time(参数)(demo1)

下面我们来证明一下:

✔ 举例:有参数装饰器执行时间

该参数是调用者的名字。

import time
​
def caller_name(name):
    def print_time(func):
        def wrapper():
            before_time = time.time()
            func(name)
            after_time = time.time()
            print(after_time - before_time)return wrapper
​
    return print_time
​
@caller_name(name="可乐")
def demo1(name):
    print(f"调用者的名字是{name}")
    for i in range(100):
        pass
demo1()# 输出 可乐  4.81秒

✔ 详细拆解

import time
​
def caller_name(name):
    def print_time(func):
        def wrapper():
            before_time = time.time()
            func(name)
            after_time = time.time()
            print(after_time - before_time)return wrapper
​
    return print_time
​
def demo1(name):
    print(f"调用者的名字是{name}")
    for i in range(100):
        pass
# 此时如果我们需要打印 demo1 的执行时间和调用者的名字,那么只需要这样调用
print_time = caller_name("kele")
wrapper = print_time(demo1)
wrapper()
# 由此得出上述我们的推断是正确的。

❗ 在 python 中除了函数装饰器,还有类装饰器。由于篇幅的原因,可乐就先不在本篇说明(后续可乐时间宽裕时,会加篇补充)。

提示:在开始深拷贝和浅拷贝之前,需要先了解引用、可变类型和不可变类型。可乐在这里就不再描述了,如果不清楚的,可以往回看Python基础 - 列表和元祖部分,可乐有详细解释。

三、拷贝

解释:拷贝就是将一个变量的值传给另外一个变量。

3.1 深拷贝

解释:深拷贝是指原对象和拷贝对象完全独立,对其中任何一个对象的改动都不会对另外一个对象有影响。

看了上面的定义,有的同学可能还有一些疑惑。可乐就来举一个🌰:
  假设工厂就是一块内存,工厂先造出来一个箱子 A ,这个时候工厂又按照 A 的样子造出来箱子 B ,那么我们对箱子 A 放入一个苹果,对箱子 B 毫无影响,给箱子 B 放入一只猫,也对箱子A没有影响。

✔ 举例1:用一段代码来演示深拷贝

import copy
​
box_a = [1, ["可乐", 18]]
box_b = copy.deepcopy(box_a)  
# 解释一下:可以通过 copy 包的 deepcopy 方法来实现深拷贝print(f"box_a内层列表的内存地址是{id(box_a[1])}")
print(f"box_a内层列表的内存地址是{id(box_b[1])}")
​
box_a[1][1] = 20
print(f"改变后box_a的值是{box_a}")
print(f"改变后box_b的值是{box_b}")

结果如下图:

✔ 举例2

import copy
​
box_a = [1, ["可乐", 18]]
box_b = copy.deepcopy(box_a)  
# 解释一下:可以通过 copy 包的 deepcopy 方法来实现深拷贝print(f"box_a内层数字1的内存地址是{id(box_a[0])}")
print(f"box_b内层数字1内存地址是{id(box_b[0])}")
​
box_a[0] = 2
print(f"改变后box_a的值是{box_a}")
print(f"改变后box_b的值是{box_b}")

结果如下图:

✔ 解释

解释上面深拷贝的情况以及注意点:
① 在深拷贝时如果拷贝的是不可变类型,那么拷贝出来的仍然是其引用。
② 在深拷贝时如果拷贝对象时可变类型,那么拷贝出来的是他的值而非引用。

3.2 浅拷贝

解释:浅拷贝是指拷贝对象对原对象最外层拷贝,内部元素拷贝的是他的引用,当修改拷贝对象或者原对象时会相互影响。

还是用箱子来举个🌰:
  假设工厂就是一块内存,工厂先造出来一个箱子 A,这个时候工厂又按照 A 的样子造出来箱子 B ,可是在造箱子 B 的时候使得箱子 A 和 B 有一部分连体了(类似于连体婴儿),这个时候会发现当给箱子 A 放入一个苹果时,会影响到箱子 B ;当给箱子 B 放入猫时也会影响到 A 。

✔ 举例1

import copy
​
box_a = [1, ["可乐", 18]]
box_b = copy.copy(box_a)  
# 解释一下:可以通过 copy 包的 copy 方法来实现浅拷贝print(f"box_a内层数字1的内存地址是{id(box_a[0])}")
print(f"box_b内层数字1内存地址是{id(box_b[0])}")
​
box_a[0] = 2
print(f"改变后box_a的值是{box_a}")
print(f"改变后box_b的值是{box_b}")

结果如下图:

✔ 举例2

import copy
​
box_a = [1, ["可乐", 18]]
box_b = copy.copy(box_a)  
# 解释一下:可以通过 copy 包的 copy 方法来实现浅拷贝print(f"box_a内层列表的内存地址是{id(box_a[1])}")
print(f"box_b内层列表内存地址是{id(box_b[1])}")
​
box_a[1][1] = 20
print(f"改变后box_a的值是{box_a}")
print(f"改变后box_b的值是{box_b}")

结果如下图:

✔ 举例3

import copy
​
box_a = [1, ["可乐", 18]]
box_b = copy.copy(box_a)  
# 解释一下:可以通过 copy 包的 copy 方法来实现浅拷贝print(f"box_a内层列表的内存地址是{id(box_a[0])}")
print(f"box_b内层列表内存地址是{id(box_b[0])}")
​
box_a[0] = 20
print(f"改变后box_a的值是{box_a}")
print(f"改变后box_b的值是{box_b}")
print(f"box_a内层列表的内存地址是{id(box_a[0])}")
print(f"box_b内层列表内存地址是{id(box_b[0])}")

结果如下图:

✔ 解释

解释上面浅拷贝的情况以及注意点:
① 在浅拷贝中,无论内层对象是可变类型还是不可变类型,拷贝出来的都是其引用。
② 无论是深拷贝还是浅拷贝,如果拷贝对象的不可变类型,那么修改该可变类型之后,不会影响另外一个对象。
( 原因可乐就不在这篇描述了,可以在之前可乐的Python基础 - 列表和元祖部分中查找 )

✔ 补充

无论是深拷贝还是浅拷贝都会对原对象拷贝,区别在于其内层对象拷贝的是引用还是值

如果大家感兴趣的话,可以扫描下方二维码关注一下可乐的公众号,就是对可乐的最大支持;公众号后面会时不时分享可乐平时遇到的问题、学习心得以及平常开发中的一些项目设计。还有最最最重要的就是关注可乐不迷路💚。


< END>

在这里插入图片描述

评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值