高阶技巧
一.闭包
可以保存函数内变量,不会随着函数调用完而销毁
(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("终端命令")