🎯 你正在阅读「Python 从零摸索日记」系列文章 🎯


🔥 弹简特 个人主页

❄️ 个人专栏直通车:

靠热爱去书写自己,靠勇敢去书写生活!


🌟 博主简介:


在这里插入图片描述



一、装饰器

1.1 定义

核心一句话:
不改动原有函数的源代码、不修改原函数调用方式,就能给函数额外加新功能,遵循开闭原则(对外扩展开放、对内源码封闭,里面的代码你不能改),这就是装饰器。

生活化举例:
你有一杯白开水(原函数,原有功能:喝水),不想倒掉白开水、不改变“拿杯子喝水”的用法,直接往里面加柠檬/冰糖(新增功能),这个“加配料的操作”就等价于装饰器。

装饰器本质是高阶函数:

一个函数,如果能接收别的函数当参数,或者能返回一个新函数,它就叫高阶函数。

说白了:装饰器就是一个函数,它的“原料”是另一个函数。

1.2 装饰器的类型

  • 函数的装饰器(常用)
  • 类的装饰器
  • 方法装饰器

1.3 装饰器的构造

  • 装饰器本身是一个函数
  • 装饰器的返回值一定是一个函数的引用(就是函数地址,这个地址存在函数名中)
  • 装饰器有且只有一个固定的形参
    • 装饰器定义的时候,括号里只能写1个形参,不能多不能少
    • 这个参数专门用来接住你要包装的那个原始函数地址。
      例:def deco(原函数): 这里就一个参数。
  • 装饰器本质是闭包
    • 装饰器里面会嵌套一个内层函数,内层函数能留住外面「原函数」这个变量,哪怕外层装饰器运行结束了,内层新函数依旧能调用原来的函数,这种嵌套留变量的结构就叫闭包,所以装饰器=闭包

一句话理清楚:

装饰器就是一个函数,它会把你原本就写好、需要添加功能的那个函数拿过来,用自己的参数接住这个原函数,也就是接收原函数的地址,在内部给它包上一层新功能,最后把包装好的新函数地址返回给你,让你以后调用原函数时,实际运行的就是加了功能的新函数。

1.4 装饰器的基本定义和使用

1.4.1 使用

首先看一个没有被加强的函数

# 装饰器的使用

def shopping():
    print('进入商城主页')
    print('开始购物')


# 调用购物函数
shopping()

在这里插入图片描述


那么现在需求就是:我要对这个函数进行加强,就是在购物之前需要用户进行登录才可以,但是我不修改已经定义好的函数,此时我们就可以使用装饰器来完成这样的一个事情,如下:

def login(func):
    def inner():
        print('请登录:')
        print('登录成功,即将跳到商城首页')
        # 对购物方法加强
        func()

    return inner
def shopping():
    print('进入商城主页')
    print('开始购物')


# 此时我们就调用装饰器函数,将shopping函数加强
shopping_plus = login(shopping)
shopping_plus()
# 结果:
# 请登录:
# 登录成功,即将跳到商城首页
# 进入商城主页
# 开始购物

在这里插入图片描述

1.4.2 一张图解释原理

在这里插入图片描述

1.5 装饰器的语法糖

我们上述使用装饰器的时候是需要自己去调用装饰器函数的,但是我们的代码步骤都是固定的,所以这个调用的过程我们有一个语法糖来为我们实现
语法@装饰器函数
示例:

def login(func):
    def inner():
        print('请登录:')
        print('登录成功,即将跳到商城首页')
        # 对购物方法加强
        func()

    return inner

@login
def shopping():
    print('进入商城主页')
    print('开始购物')


shopping()

结果:
在这里插入图片描述

解释:
在这里插入图片描述
注意事项 :装饰器函数必须定义在我们的被装饰函数的前面,否则会报错

# 装饰器函数必须定义在我们的被装饰函数的前面,否则会报错
@login # 报错:name 'login' is not defined
def shopping():
    print('进入商城主页')
    print('开始购物')


def login(func):
    def inner():
        print('请登录:')
        print('登录成功,即将跳到商城首页')
        # 对购物方法加强
        func()

    return inner
shopping()

在这里插入图片描述

1.6 装饰器使用的案例

案例: 通过装饰器来获取一个函数的执行时间


import time

# 使用装饰器假期我们的func函数:注意装饰器函数必须写在前面
def out_func(func):
    def inner():
        # 函数执行前的时间戳
        begin_time = time.time()
        # 执行函数
        func()
        # 函数执行之后的时间戳
        end_time = time.time()
        # 计算函数的执行时间
        print(f'函数的执行时间:{end_time - begin_time}')

    # 注意:最后不要忘记了还有返回值
    return inner

# 被加强的函数
@out_func
def func():
    num_list = []

    for i in range(1, 10000001):
        num_list.append(i)

# 调用函数
func()

在这里插入图片描述

结果:
在这里插入图片描述


二、反射的机制

2.1 反射的认识

  • 反射是基于面向对象类的封装和对象的创建的
  • 使用反射,我们可以通过字符串类型对对象中具体某个属性或者方法使用
  • 使用反射,我们可以实现对象的属性或者方法进行增删改查

解释
正常是直接写名字调用对象的属性/方法,反射反过来:拿着「字符串文字」就能找到、增删改对象里对应的属性或函数。

拆开通俗解释

  1. 普通用法:obj.name,实打实写好属性名字name才能取值;
  2. 反射:手里只有字符串"name"这几个文字,照样能找到对象里叫name的属性,查值、改值、删属性、新加方法全都能做;
  3. 底层依托类和对象的封装,Python靠getattr/setattr/delattr/hasattr四个内置函数实现这个字符串匹配功能。

生活化举例
手机(对象)自带【打电话】功能,正常点开图标(直接写方法名phone.call()),反射就是只输入文字"call",系统凭这串文字自动打开打电话功能,还能新增APP、卸载APP、修改APP参数。

2.2 反射的使用-添加或者覆盖(修改)

2.2.1 添加

案例: 给x这个对象,新增一个新的方法
前提: 要有这个方法或者这个方法的引用,也就是必须有这个方法
语法: setattr(对象,方法名,函数引用)

  • 方法名必须是字符串形式,以字符串参数传递
  • 函数引用可以是自定义的函数地址,也可以内建函数的地址(地址存在我们的函数名中)

示例: 内建函数

class A:
    def a(self):
        print('调用实例方法a~')

    def b(self):
        print('调用实例方法b~')

    def c(self):
        print('调用实例方法c~')

    def d(self):
        print('调用实例方法d~')


# 实例化一个对象
x = A()

# 需求:给这个x对象新增一个输入的方法(内建函数print)
setattr(x, 'e', print)
x.e('我是x对象新增的e方法')

结果:
在这里插入图片描述

示例: 自定义函数

# 添加自定义函数
class A:
    def a(self):
        print('调用实例方法a~')

    def b(self):
        print('调用实例方法b~')

    def c(self):
        print('调用实例方法c~')

    def d(self):
        print('调用实例方法d~')


# 自己定义一个函数
def func(name):
    print(f'我是新增的函数,我的名字是{name}')

# 实例化一个对象
x = A()

# 需求:给这个x对象新增一个自定义的方法
setattr(x, 'f', func)
x.f('弹简特')

结果:
在这里插入图片描述

2.2.1 覆盖(修改)

案例: x这个对象,覆盖这个对象原来的方法,使用新方法的功能
前提: 要有这个新方法或者这个新方法的引用,也就是必须有这个新方法
语法: setattr(对象,对象中被覆盖的方法名,函数引用)

  • 对象中被覆盖的方法名必须是字符串形式,以字符串参数传递
  • 函数引用可以是自定义的函数地址,也可以内建函数的地址(地址存在我们的函数名中)

示例:

# x这个对象,覆盖这个对象原来的方法,使用新方法的功能
# 添加自定义函数
class A:
    def a(self):
        print('调用实例方法a~')

    def b(self):
        print('调用实例方法b~')

    def c(self):
        print('调用实例方法c~')

    def d(self):
        print('调用实例方法d~')


# 自己定义一个函数
def func():
    print('我是覆盖a函数的新函数,我的名字是 李四')

# 实例化一个对象
x = A()
print('覆盖之前:')
x.a()
# 结果:
# 覆盖之前:
# 调用实例方法a~
print('---'*100)
print('覆盖之后:')
setattr(x, 'a', func)
x.a()
# 结果:
# 覆盖之后:
# 我是覆盖a函数的新函数,我的名字是 李四

结果:
在这里插入图片描述

2.3 反射的使用-删除属性和方法

2.3.1 删除实例属性

案例: 给x这个对象,删除这个对象里面的money实例属性
语法: delattr(对象名,'实例属性名')

示例:

class A:

    def __init__(self, money):
        # 实例属性
        self.money = money

    def a(self):
        print('调用实例方法a~')

    def b(self):
        print('调用实例方法b~')

    def c(self):
        print('调用实例方法c~')

    def d(self):
        print('调用实例方法d~')



# 实例化一个对象
x = A(100)
# 需求:删除x这个对象的实例属性money
# 删除之前 可以调用实例属性
print(x.money)
# 开始删除实例属性
delattr(x, 'money')
# 删除之后实例属性调用不了
print(x.money) # 报错结果:AttributeError: 'A' object has no attribute 'money'

2.3.2 删除类属性

案例: 给x这个对象,删除这个对象里面的name类属性
语法: delattr(对象名,'类属性名')

示例:

class A:
    # 类属性
    name = '李四'
    def a(self):
        print('调用实例方法a~')

    def b(self):
        print('调用实例方法b~')

    def c(self):
        print('调用实例方法c~')

    def d(self):
        print('调用实例方法d~')



# 实例化一个对象
x = A()
# 需求: 删除x这个对象里面的name类属性
# 删除之前 可以调用类属性
print(A.name)
# 开始删除类属性
delattr(x, 'name')
# 删除之后调用不了类属性
print(A.name) # 报错结果:AttributeError: 'A' object has no attribute 'name'

2.3.3 删除实例方法

案例: 给x这个对象,删除这个对象里面的实例a方法
语法: delattr(对象名,'实例方法名')

示例:

# 删除实例方法
class A:
    def a(self):
        print('调用实例方法a~')

    def b(self):
        print('调用实例方法b~')

    def c(self):
        print('调用实例方法c~')

    def d(self):
        print('调用实例方法d~')



# 实例化一个对象
x = A()
# 需求: 删除x这个对象里面的a实例方法
# 删除之前 可以调用实例方法
x.a()
# 开始删除实例方法
delattr(x, 'a')
# 删除之后调用不了实例方法
x.a() # 报错结果:AttributeError: 'A' object has no attribute 'a'

2.3.4 删除类方法

案例: 给x这个对象,删除这个对象里面的累e方法
语法: delattr(对象名,'类方法名')

示例:

# 删除类方法
class A:
    def a(self):
        print('调用实例方法a~')

    def b(self):
        print('调用实例方法b~')

    def c(self):
        print('调用实例方法c~')

    def d(self):
        print('调用实例方法d~')

    # 定义一个类方法
    @classmethod
    def e(cls):
        print('我是一个类方法')


# 实例化一个对象
x = A()
# 需求: 删除x这个对象里面的e类方法
# 删除之前 可以调用实例方法
A.e()
# 开始删除实例方法
delattr(x, 'e')
# 删除之后调用不了实例方法
A.e() # 报错结果:AttributeError: 'A' object has no attribute 'e'

2.4 反射的使用-查询

案例: x这个对象,删除这个对象中是否有属性或者方法
语法: hasattr(对象名,'属性或者方法名')
返回值: hasattr()返回的是布尔类型
示例:

class A:

    # 实例属性
    name = 'tan'

    # 初始化实例属性的函数
    def __init__(self, money):
        self.money = money

    # 类方法
    @classmethod
    def e(cls):
        print('我是类方法e~')

    # 实例方法
    def a(self):
        print('调用实例方法a~')

    def b(self):
        print('调用实例方法b~')

    def c(self):
        print('调用实例方法c~')

    def d(self):
        print('调用实例方法d~')


# 实例化一个对象
x = A(200)

# 查询是否有实例属性:有
print(hasattr(x, 'money')) # True
# 查询是否有实例方法:有
print(hasattr(x, 'a')) # True
# 查询是否有类属性:有
print(hasattr(x, 'name')) # True
# 查询是否有类方法:有
print(hasattr(x, 'e')) # True

# 查询是否有实例属性:没有
print(hasattr(x, 'money_no')) # False
# 查询是否有实例方法:没有
print(hasattr(x, 'f')) # False
# 查询是否有类属性:没有
print(hasattr(x, 'name_no')) # False
# 查询是否有类方法:没有
print(hasattr(x, 'e_no')) # False 

2.5 反射读取属性或方法

2.5.1 使用

案例: 可以使用getattr()反射把我们对象的属性或者方法传递给指定变量进行使用
语法: getattr(对象名,'属性或者方法名')
返回值: getattr()返回的是对应的属性值或者方法的地址值

示例:

class A:

    # 实例属性
    name = 'tan'

    # 初始化实例属性的函数
    def __init__(self, money):
        self.money = money

    # 类方法
    @classmethod
    def e(cls):
        print('我是类方法e~')

    # 实例方法
    def a(self):
        print('调用实例方法a~')



# 实例化一个对象
x = A(200)

# 实例属性
m_i = getattr(x, 'money')
print(m_i) # 200
# 实例方法
a_i = getattr(x, 'a')
a_i() # 调用实例方法a~
# 类属性
name_c = getattr(x, 'name')
print(name_c) # tan
# 类方法
e_c = getattr(x, 'e')
e_c() # 我是类方法e~

2.5.2 扩展技巧

扩展: 将我们的所有方法的名字放到列表中,通过遍历列表反射提取掉用
示例:

class A:
    # 实例方法
    def a(self):
        print('调用实例方法a~')

    def b(self):
        print('调用实例方法b~')

    def c(self):
        print('调用实例方法c~')

    def d(self):
        print('调用实例方法d~')


x = A()

# 将我们的所有方法的名字放到列表中,通过遍历列表反射提取掉用
for i in ['a', 'b', 'c', 'd']:
    f = getattr(x, i)
    f()

结果:
在这里插入图片描述


文章有用欢迎点赞收藏,专栏持续更新Python与软件测试干货,不足之处欢迎评论指正。

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐