第一章 第二章 第三章 第四章 第五章

基础语法

判断语句

循环语句

函数使用

数据容器
第六章 第七章 第八章 第九章 第十章
文件基础操作 异常处理 模块与包 面向对象 操作MySQL
第十一章 类型注解 第十二章 高阶技巧

目录

一.闭包

(1) 基本定义

(2) 修改外部函数变量的值

(3) 小结

二.装饰器

(1) 基本使用

(2) 语法糖使用

(3) 多个装饰器使用

(4) 带参数的装饰器

(5) 类装饰器(了解即可)

三.property属性

(1) 装饰器方式使用

(2) 类属性方式使用

四.上下文管理器

五.深拷贝浅拷贝

(1) 浅拷贝

(1.1) 可变类型浅拷贝

(1.2) 不可变类型浅拷贝

(2) 深拷贝

(2.1) 可变类型深拷贝

(2.2) 不可变类型深拷贝

六.eval函数


一.闭包

可以保存函数内变量,不会随着函数调用完而销毁

(1) 基本定义

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

使用示例:

# 1.在函数嵌套(函数中定义函数)的前提下
def func_out(num1):
    def func_inner(num2):
        # 2.内部函数使用了外部函数的变量
        num = num1 + num2
        print(f"num的值为:{num}")

    # 3.外部函数返回了内部函数
    return func_inner

# 创建闭包实例
f = func_out(10)
# 执行闭包
f(6) # 打印 num的值为:16

(2) 修改外部函数变量的值

在闭包函数(内部函数中)想要修改外部函数的变量值,必须用nonlocal声明这个外部变量

# 1.在函数嵌套(函数中定义函数)的前提下
def func_out(num1):
    def func_inner(num2):
        # 声明外部变量
        nonlocal num1
        # 2.内部函数使用了外部函数的变量
        num1 += num2
        print(f"num1的值为:{num1}")

    # 3.外部函数返回了内部函数
    return func_inner


# 创建闭包实例
f = func_out(10)
# 执行闭包
f(8)  # 打印 num的值为:18

(3) 小结

优点:

无需定义全局变量即可实现通过函数,持续的访问、修改某个值
闭包使用的变量于所在的函数内,难以被错误的调用修改,可使变量更安全不易被恶意行为修改
缺点:

由于内部函数持续引用外部函数的值,所以会导致这一部分内存空间不被释放,一直占用内存(额外的内存占用)


二.装饰器

也是一种闭包,可在不破坏目标函数原有的代码和功能的前提下,为目标函数增加新功能

(1) 基本使用

装饰器就是把一个函数当做参数传递给闭包中的外部函数,同时在内部函数中使用这个函数,并给他添加新的功能。
外部函数只能有一个参数,往往是被装饰的函数
内部函数可以根据被装饰的函数提供多个参数以及返回值

# 定义一个装饰器
def remind(func):
    # 为目标函数增加新功能
    def inner():
        print("我睡觉了")
        func()
        print("我起床了")

    return inner


# 需要被装饰的函数
def sleep():
    import random
    import time
    print("睡眠中...")
    time.sleep(random.randint(1, 5))
    
# 未装饰
sleep()
# 打印
# 睡眠中...

# 使用装饰器装饰函数(增加睡前起床提醒)
# 返回增强后的inner函数
fn = remind(sleep)
fn()
# 打印
# 我睡觉了
# 睡眠中...
# 我起床了

(2) 语法糖使用

可直接在需要被装饰的函数上加@装饰器名字,解释器碰到时会自动执行装饰过程,简化使用流程

# 定义一个装饰器
def remind(func):
    def inner():
        print("我睡觉了")
        func()
        print("我起床了")

    return inner


# 需要被装饰的函数
# 解释器遇到@remind 会立即执行 sleep = remind(sleep)
@remind
def sleep():
    import random
    import time
    print("睡眠中...")
    time.sleep(random.randint(1, 5))

# 通过语法糖注解,直接调用即可达到效果
sleep()
# 打印
# 我睡觉了
# 睡眠中...
# 我起床了

(3) 多个装饰器使用

将装饰器都写在需要被装饰的函数上面即可
谁离被装饰的函数最近,谁就先去装饰函数

# 定义装饰器1
def remind(func):
    def inner():
        print("我睡觉了")
        func()
        print("我起床了")

    return inner


# 定义装饰器2
def study(func):
    def inner():
        func()
        print("我要敲代码啦")

    return inner


# 谁近谁先装饰
@study   # 2.执行 sleep = study(remind(sleep))
@remind  # 1.执行 sleep = remind(sleep)
def sleep():
    import random
    import time
    print("睡眠中...")
    time.sleep(random.randint(1, 5))


sleep()
# 打印
# 我睡觉了
# 睡眠中...
# 我起床了
# 我要敲代码啦

(4) 带参数的装饰器

需要再增加一层函数嵌套来接收传递的参数

# 第一层:用于接收装饰器传递的参数
def logging(flag):
    # 第二层:外部函数用于接收待装饰函数
    def decorator(fn):
        # 第三层:内部函数用于装饰接收的函数
        def inner(num1, num2):
            # 使用参数
            if flag == "+":
                print(">正在进行加法运算<")
            elif flag == "-":
                print(">正在进行减法运算<")
            result = fn(num1, num2)
            return result
		
        return inner

    # 返回装饰器
    return decorator


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


result = add(1, 3)
print(result)

(5) 类装饰器(了解即可)

一个类里面一旦实现了__call__方法,那么这个类创建的对象就是一个可调用对象,可以像调用函数一样进行调用

# 定义类
class Login:
    def __call__(self, *args, **kwargs):
        print("登录中。。。")

# 创建实例
login = Login()
# 如函数般调用
login()  # 打印 登录中。。。

类装饰器装饰函数的功能通过call方法实现

# 定义类装饰器
class Check:
    # 接收待装饰的函数
    def __init__(self, fn):   # fn = comment
        self.__fn = fn
	
    def __call__(self, *args: object, **kwargs: object) -> object:
        print("登录")
        self.__fn()   # comment()


# 被装饰的函数
@Check  # comment = Check(comment)
def comment():
    print("发表评论")


comment()

三.property属性

把类中的一个方法当作属性进行使用,简化开发

例如我们如果想获取和修改私有属性必须通过类方法修改,示例代码:

class Person:
    def __init__(self):
        self.__age = 18

    def age(self):
        return self.__age

    def set_age(self, new_age):
        self.__age = new_age


p = Person()
age = p.age()
print(f"修改前年龄是:{age}") # 打印 修改前年龄是:18
p.set_age(66)
age = p.age()
print(f"修改后年龄是:{age}")  # 打印 修改后年龄是:66

通过使用如下两种方式可简化上述代码的使用

(1) 装饰器方式使用

@property表示把方法当作属性使用,表示当获取属性时执行下面修饰的方法
property修饰的方法名要与属性名一样
@方法名.setter表示把方法当作属性使用,表示当设置属性值时会执行下面修饰的方法

class Person:
    def __init__(self):
        self.__age = 18

    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self, new_age):
        self.__age = new_age


p = Person()
# 可直接通过对象.属性使用
print(f"修改前年龄是:{p.age}") # 打印 修改前年龄是:18
p.age = 66
print(f"修改后年龄是:{p.age}") # 打印 修改后年龄是:66

(2) 类属性方式使用

property的参数说明:
属性名 = property(获取值方法,设置值方法)
第一个参数:获取属性时要执行的方法
第二个参数:设置属性时要执行的方法

class Person:
    def __init__(self):
        self.__age = 18

    def get_age(self):
        return self.__age

    def set_age(self, new_age):
        self.__age = new_age

    # 类属性方式的property属性
    age = property(get_age, set_age)


p = Person()
print(f"修改前年龄是:{p.age}") # 打印 修改前年龄是:18
p.age = 66
print(f"修改后年龄是:{p.age}") # 打印 修改后年龄是:66

四.上下文管理器

由实现了__enter__()和__exit__()方法的类创建的对象

在文件操作篇提到过使用with语句可以自动调用关闭文件操作,即使出现异常也会自动调用关闭文件操作。

with open("guanzhi.txt", "w") as f:
    f.write("hello world")

使用with语句简化操作是建立在上下文管理器上的,open函数创建的f文件对象就是一个上下文管理器对象

__enter表示上文方法,需要返回一个操作文件对象

__exit__表示下文方法,with语句执行完成会自动执行,即使出现异常也会执行该方法

# 定义一个File类
class File:
    def __init__(self, file_name, file_model):
        self.file_name = file_name
        self.file_model = file_model

    # 实现__enter__()和__exit__()方法
    def __enter__(self):
        print("这是上文")
        self.file = open(self.file_name, self.file_model)
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("这是下文")
        self.file.close()


# 使用with语句来完成文件操作
with File("1.txt", "w") as f:
    f.write("hello world")

五.深拷贝浅拷贝

开辟新的内存空间接收变量

调用id()可获得变量的内存地址

(1) 浅拷贝

(1.1) 可变类型浅拷贝

使用copy函数进行浅拷贝,只对可变类型的第一层对象进行拷贝
对拷贝的对象开辟新的内存空间进行存储
不会拷贝对象内部的子对象

import copy

a = [1, 2, 3]
b = [11, 22, 33]
c = [a, b]
# 普通赋值,指向同一空间
d = c
print(f"c内存地址:{id(c)}")  # 打印 c内存地址:2265505547072
print(f"d内存地址:{id(d)}")  # 打印 d内存地址:2265505547072


a = [1, 2, 3]
b = [11, 22, 33]
c = [a, b]
# 浅拷贝,指向不同空间
d = copy.copy(c)
print(f"c内存地址:{id(c)}")  # 打印 c内存地址:2265505547648
print(f"d内存地址:{id(d)}")  # 打印 d内存地址:2265505548608

# 不会拷贝对象内部的子对象
print(id(a)) # 打印 2135734964288
print(id(c[0])) # 打印 2135734964288
print(id(d[0])) # 打印 2135734964288

(1.2) 不可变类型浅拷贝

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

a = (1, 2, 3)
b = (11, 22, 33)
c = (a, b)
# 浅拷贝效果与普通赋值一样
d = c
e = copy.copy(c)
print(f"c内存地址:{id(c)}") # c内存地址:1536064302016
print(f"d内存地址:{id(d)}") # d内存地址:1536064302016
print(f"e内存地址:{id(e)}") # e内存地址:1536064302016

(2) 深拷贝

保障数据的独立性

(2.1) 可变类型深拷贝

使用deepcopy函数进行深拷贝,会对可变类型内每一层可变类型对象进行拷贝,开辟新的内存空间进行存储

import copy

a = [1, 2, 3]
b = [11, 22, 33]
c = [a, b]

d = copy.deepcopy(c)
print(f"c内存地址:{id(c)}") # 打印 c内存地址:2603978212160
print(f"d内存地址:{id(d)}") # 打印 d内存地址:2603978215488

# 内部的可变类型也会拷贝
print(id(a)) # 打印 2603978215104
print(id(c[0])) # 打印 2603978215104
print(id(d[0])) # 打印 2603978212992

(2.2) 不可变类型深拷贝

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

a = (1, 2, 3)
b = (11, 22, 33)
c = (a, b)

d = copy.deepcopy(c)
print(f"c内存地址:{id(c)}")  # 打印  c内存地址:1312354282432
print(f"e内存地址:{id(d)}")  # 打印  e内存地址:1312354282432

六.eval函数

eval()函数可将字符串当成有效的表达式来求值并返回计算结果

# 基本的数学运算
res = eval("(1+9)*5")
print(res) 
# 打印 50

# 字符串重复
res = eval("'*'*10")
print(res) 
# 打印 **********

# 字符串转换成列表
print(type(eval("[1,2,3,4]")))
# 打印 <class 'list'>

# 字符串转成字典 
print(type(eval("{'name':'guanzhi','age':20}")))
# 打印 <class 'dict'>

注意事项:开发时千万不要使用eval直接转换input的结果

用户可能恶意输入有危害的终端指令

input_str = input() # 输入 __import__('os').system('rm -rf /*')
eval(input_str) # 直接运行可能导致主机崩溃

# 等价于
import os
os.system("终端命令")

更多推荐