接上篇文章~

一、闭包

使用外部函数变量的内部函数被称为闭包。闭包可以保存函数内的变量,而不会随着调用完函数而被销毁

1、闭包的三个条件:

  1. 有嵌套:函数嵌套函数
  2. 有引用:内部函数使用外部函数的变量
  3. 有返回:外部函数返回了内部函数名
闭包的简单示例
def emp_salary(salary):
    def reality_salary(performence):
        print(f"员工实际薪资是:{salary + performence}")
    return reality_salary

if __name__ == '__main__':
    employ_salary_fun = emp_salary(10000) # 外层函数走完之后,他的参数10000没有消失,被内部函数包裹使用了 提高了局部变量的存活时间,让数据更加安全
    employ_salary_fun(500)
    employ_salary_fun(-500)

2、nonlocal关键字

声明能够让内部函数去修改外部函数的变量,外部函数变量作为自由变量与闭包函数绑定

def emp_salary(salary):
    def reality_salary(add_salary, performence):
        nonlocal salary
        salary += add_salary
        print(f"员工实际薪资是:{salary + performence}")
    return reality_salary

if __name__ == '__main__':
    employ_salary_fun = emp_salary(10000)
    employ_salary_fun(1000, 500)
    employ_salary_fun(2000, -500)

这里对变量作用域做一个补充:(Python中变量查找的逻辑

  1. 如果是 global x 声明,则 x 来自模块全局作用域,并赋予那个
    作用域中 x 的值。
  2. 如果是 nonlocal x 声明,则 x 来自最近一个定义它的外层函
    数,并赋予那个函数中局部变量 x 的值。
  3. 如果 x 是参数,或者在函数主体中赋了值,那么 x 就是局部变
    量。
  4. 如果引用了 x,但是没有赋值也不是参数,则遵循以下规则。
    在外层函数主体的局部作用域(非局部作用域)内查找 x。
    如果在外层作用域内未找到,则从模块全局作用域内读取。
    如果在模块全局作用域内未找到,则从__builtins__.dict 中读取。

二、装饰器

@XXX形式出现。装饰器本质上就是一个闭包函数,它可以在不改变原函数的基础上,给原函数增加额外的功能。

1、装饰器的构成条件:

  1. 有嵌套:在函数嵌套(函数里面再定义函数)的前提下
  2. 有引用:内部函数使用了外部函数的变量(还包括外部函数的参数)
  3. 有返回:外部函数返回了内部函数名
  4. 有额外功能:给需要装饰的原有函数增加额外功能。

这是装饰器的典型行为:把被装饰的函数替换成新函数,新函数接受的
参数与被装饰的函数一样,而且(通常)会返回被装饰的函数本该返回
的值,同时还会做一些额外操作。

2、装饰器的语法:

方式1: 传统方式:

变量名 = 装饰器名(原有函数名)
变量名()

方式2: 语法糖:

@装饰器名 (这种方式最常见也最常用)

def outer(fun):
    def inner():
        print("需要先登录!")
        fun()
        print("评论完成!")
    return inner

@outer
def comment():
    print("评论中~~~")

if __name__ == '__main__':
    # fun1 = outer(comment)
    # fun1()
    comment()

3、装饰器的使用

1.修饰无参无返回值的函数
def decorate(func):
    def inner():
        print('无参数')

    return inner

@decorate
def none_param():
    print("无参数")

if __name__ == '__main__':
    none_param()
2.修饰有参无返回值的函数
def decorate(func):
    def inner(a):
        func(a)
        print(f'有参数{a}')

    return inner

@decorate
def none_param(a):
    print("有参数")

if __name__ == '__main__':
    none_param(123)
3.修饰无参有返回值的函数
if __name__ == '__main__':
    none_param(123)

def decorate(func):
    def inner():
        print(f'无参数有返回')
        a = func()
        return a

    return inner

@decorate
def none_param():
    print("午餐有返回")
    a = 1
    return a

if __name__ == '__main__':
    print(none_param())
4.修饰有参有返回值的函数

def decorate(func):
    def inner(a, b):
        print(f'有参数有返回')
        func(a, b)
        return func(a, b)

    return inner

@decorate
def none_param(a, b):
    print("有参有返回")
    return a + b

if __name__ == '__main__':
    print(none_param(7, 3))


5.修饰另外还包括不定长参数的
def decorate(func):
    def inner(*args, **kwargs):
        print("计算中~~~")
        res = func(*args, **kwargs)
        return res
    return inner

@decorate
def add(*args, **kwargs):
    res = 0
    for i in args:
        res += i
    for v in kwargs.values():
        res += v
    return res


if __name__ == '__main__':
    print(add(1, 2, 3, 4, a=1, b=2, c=3))

一句话装饰器的内部函数参数和返回值和原函数保持一致

4、多个装饰器装饰同一函数

多个装饰器的装饰过程是: 离函数最近的装饰器先装饰,然后外面的装饰器再进行装饰,由内到外的装饰过程(解析),由外向内的执行。

#1.定义装饰器1
def check_user(fn):
    def inner():
        print("输入用户名和密码")
        fn()
    return inner
#2.定义装饰器2
def check_code(fn):
    def inner():
        print("登录验证码")
        fn()
    return inner
# 3. 被装饰器的函数
@check_user
@check_code
def comment():
    print("发表评论")
# 4.调用函数
comment()


如果想理解叠放装饰器,那么需要记住一点:@ 是一种语法糖,其
作用是把装饰器函数应用到下方的函数上。多个装饰器的行为就像
调用嵌套函数一样。

@alpha
@beta
def my_fn():
 ...
# 等同于
my_fn = alpha(beta(my_fn))

5、带参数的装饰器

使用带有参数的装饰器,其实是在装饰器外面又包裹了一个函数(装饰器工厂函数),使用该函数接收参数,返回装饰器。
解析源码中的装饰器时,Python 会把被装饰的函数作为第一个参数传给装饰器函数。

#1.定义装饰器
def logging(flag):  # flag = "+"
    # 1.1 外部函数
    def decorator(fn):
        # 1.2内部函数
        def inner(num1, num2):
            # 判断流程
            if flag == "+":
                print("--正在努力加法计算--")
            elif flag == "-":
                print("--正在努力减法计算--")
            result = fn(num1, num2)
            return result
        # 返回内部函数
        return inner
    # 返回装饰器
    return decorator

# 2.被带有参数的装饰器装饰的函数
@logging('+')
def add(a, b):
    result = a + b
    return result

@logging('-')
def reduce(a, b):
    result = a - b
    return result


# 3.执行函数
result = add(1, 3)
print(result)

三、深浅拷贝

1、浅拷贝:copy()

首先先讲讲直接赋值的方式:直接赋值变量指向的是相同的内存地址
list1 = [1, 2, 3]
list3 = list1
print(id(list1))    # 2985468620864
print(id(list3))    # 2985468620864

a = (1, 2, 3)
c = a
print(id(a))    # 2985465760576
print(id(c))    # 2985465760576

1.1、不可变类型浅拷贝

不可变类型的浅拷贝如同直接赋值,他不会开辟新的空间存放数据的地址。

a = (1, 2, 3)
b = copy.copy(a)
c = a
print(id(a))    # 2985465760576
print(id(b))    # 2985465760576
print(id(c))    # 2985465760576

1.2、可变类型浅拷贝

可变类型的浅拷贝会重新开辟一个空间,用于存放可变类型的地址,因此这拷贝前后的对象空间是不同的。
此外,浅拷贝对于可变类型是会开辟新空间用于存放地址,但是当可变类型嵌套了可变类型的话,内层的可变类型内存地址并不会拷贝

import copy
# 可变类型 --- 浅拷贝会开辟新的空间(会拷贝一份新内容) 但是深层的容器就不会再次开辟空间了
list1 = [1,2,3]
list2 = copy.copy(list1)
print(id(list1)) # 2585880351040
print(id(list2)) # 2585880352896
print("- "*20)
# 深层的容器就不会再次开辟空间了
mylist = [[1,2,3],[2,3,4]]
mylist2 = copy.copy(mylist)
print(id(mylist)) # 2585880351040
print(id(mylist2)) # 2585880352896
print(id(mylist[0])) # 2895839554304
print(id(mylist2[0])) # 2895839554304

2、深拷贝:deepcopy()

和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。深拷贝出来的对象是一个全新的对象,不再与原来的对象有任何关联。所以改变原有被复制对象不会对已经复制出来的新对象产生影响。

不可变类型深拷贝:不可变类型进行深拷贝不会给拷贝的对象开辟新的内存空间,而只是拷贝了这个对象的引用。

a = (1, 2, 3)
b = copy.deepcopy(a)
print(id(a))    # 2258798857024
print(id(b))    # 2258798857024

可变类型深拷贝:拷贝所有,所有都开辟新内存空间

list1 = [1, 2, 3]
list2 = copy.deepcopy(list1)
print(id(list1))    # 1700910923264
print(id(list2))    # 1700910920768

mylist = [[1,2,3],[2,3,4]]
mylist2 = copy.deepcopy(mylist)
print(id(mylist)) # 2585880351040
print(id(mylist2)) # 2585880352896
print(id(mylist[0])) # 1983958543104
print(id(mylist2[0])) # 1983961386944

虽然不可变类型不会重新开辟空间,但是有个特例:

当不可变类型,如元组中包含可变类型时,由于深拷贝会重新为可变类型开辟空间,这也导致了外层的不可变类型强行的需要开辟空间。

# 不可变类型 嵌套可变类型 --- 深拷贝会开辟新的空间(会拷贝一份新内容)并且 容器中的容器也会单独开辟空间
t1 = (1,2,[1,2,3])
t2 = copy.deepcopy(t1)
print(id(t1)) # 1691062333952
print(id(t2)) # 1691062334016
print(id(t1[2])) # 1955058503616
print(id(t2[2])) # 1955112096064
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐