Python高阶技巧(十二)

简介: Python高阶技巧(十二)

高阶技巧

一.闭包

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

(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("终端命令")
目录
相关文章
|
8月前
|
Python
|
8月前
|
数据处理 Python
Python进阶(十二)常用数据处理模块
Python进阶(十二)常用数据处理模块
|
XML 数据格式 Python
Python高阶教程——Xpath解析
Xpath简介 XPath是一种用于在XML文档中定位节点的语言,它可以用于从XML文档中提取数据,以及在XML文档中进行搜索和过滤操作。它是W3C标准的一部分,被广泛应用于XML文档的处理和分析。 XPath使用路径表达式来描述节点的位置,这些路径表达式类似于文件系统中的路径。路径表达式由一个或多个步骤(step)组成,每个步骤描述了一个节点或一组节点。步骤可以使用关系运算符(如/和//)来连接,以便描述更复杂的节点位置。 XPath还提供了一些内置函数和运算符,可以对XML文档中的数据进行操作和计算。例如,可以使用XPath的数学函数来计算节点的数值,或使用字符串函数来处理节点的文本内
312 0
|
SQL 关系型数据库 MySQL
Python高阶教程——MySQL基础
Python3 MySQL 数据库连接 - PyMySQL 驱动 本文我们为大家介绍 Python3 使用 PyMySQL 连接数据库,并实现简单的增删改查。 PyMySQL 是在 Python3.x 版本中用于连接 MySQL 服务器的一个库,Python2 中则使用 mysqldb。 PyMySQL 遵循 Python 数据库 API v2.0 规范,并包含了 pure-Python MySQL 客户端库。
143 0
|
Python Windows 容器
Python案例篇:七个Python高阶案例(晕题的不要轻易看)
Python案例篇:七个Python高阶案例(晕题的不要轻易看)
263 0
文件的其他操作 | Python从入门到精通:高阶篇之五十二
介绍了一些文件的基本操作,包括新建文件、删除文件、切换文件所在的目录、显示文件当前所在的目录、重命名文件等一些操作。
文件的其他操作 |  Python从入门到精通:高阶篇之五十二
文件的写入 | Python从入门到精通:高阶篇之四十九
可以使用write()来向文件中写入内容,如果操作的是一个文本文件的话,则write()需要传递一个字符串作为参数。该方法可以分多次向文件中写入内容,写入完成以后,该方法会返回写入的字符的个数。
文件的写入  | Python从入门到精通:高阶篇之四十九
|
Java Python
特殊方法 | Python从入门到精通:高阶篇之三十六
本节重点介绍了一些特殊方法的使用__str__()、__repr__()、比较大小、__len__()、object.__bool__(self),另外还有一些运算符。这些特殊方法的使用都是多态的体现。
特殊方法 | Python从入门到精通:高阶篇之三十六
|
Python Windows
Python标准库 | Python从入门到精通:高阶篇之三十九
本节介绍了Python标准库中的pprint 模块、sys模块、os模块中的一些简单的用法。我们在以后的开发过程中也可以先去标准库中查找,避免重复开发,因为模块的功能很多,所以我们选择的时候也要去仔细阅读。
Python标准库 | Python从入门到精通:高阶篇之三十九
属性和方法 | Python从入门到精通:高阶篇之二十三
当我们调用一个对象的属性时,解析器会先在当前对象中寻找是否含有该属性,如果有,则直接返回当前的对象的属性值,如果没有,则去当前对象的类对象中去寻找,如果有则返回类对象的属性值,如果没有则报错!
属性和方法 | Python从入门到精通:高阶篇之二十三