包含编程资料、学习路线图、源代码、软件安装包等!【[点击这里]】!

属性控制

一、基础方法属性化
    1. 传统方法控制
1 # 1. 传统方法控制
2 class Test:
3     def __init__(self):
4         self._text = '我不想让你看见'
5 
6     # 显式操作接口
7     def get_t(self):
8         return self._text
9 
10     def set_t(self, value):
11         self._text = value
12 
13     def del_t(self):
14         del self._text
15 
16 
17 # 使用示例
18 a = Test()
19 print(a.get_t())    # 调用并查看属性
20 print(a._text)      # 也可以直接调用属性
21 a.set_t('新值')      # 显式调用方法修改属性值
22 print(a.get_t())    # 输出:新值
23 a.del_t()           # 删除属性
24 print(a.get_t())    # 删除后调用,报错:AttributeError: 'Test' object has no attribute '_text'

在这里插入图片描述

传统方法的问题;

✔️ 每次操作都要记方法名
✔️ 可以直接访问a._text不安全
✔️ 代码不够简洁直观

  • 为了能简化这些操作步骤,我们可以选择 property()函数来替代那些繁琐的步骤。
二、property()函数进阶
    1. 属性绑定方法
1 class Test:
2     """
3     保持原有方法不变
4     """
5     def __init__(self):
6         self._text = '我不想让你看见'
7 
8     def get_t(self):
9         return self._text
10 
11     def set_t(self, value):
12         self._text = value
13 
14     def del_t(self):
15         del self._text
16 
17     # 把三个方法打包成属性
18     x = property(get_t, set_t, del_t)
19 
20 
21 # 实例化对象
22 a = Test()
23 """
24 a.x 将触发 getter方法, 
25 a.x = value 将触发 setter方法, 
26 del a.x 触发 deleter方法.
27 """
28 print(a.x)  # 隐式调用get_t → 输出:我不想让你看见
29 a.x = '马甲属性'  # 隐式调用set_t(value),设置属性
30 print(a.x)  # 隐式调用get_t → 输出:马甲属性
31 del a.x     # 隐式调用del_t 删除属性
32 print(a.x)  # 调用已删除的属性

在这里插入图片描述
解决的问题:
✔️ 用属性语法替代方法调用
✔️ 隐藏真实属性名_text
✔️ 保持数据验证能力(可在方法中添加检查)

  • 2.加一层异常捕获:健壮性改造
1 class Test:
2     def __init__(self):
3         self._text = '我不想让你看见'
4 
5     # 带异常处理的访问器
6     def get_t(self):
7         try:
8             return self._text
9         except:
10             return '属性不存在'
11 
12     def set_t(self, value):
13         self._text = value
14 
15     def del_t(self):
16         del self._text
17 
18     x = property(get_t, set_t, del_t)
19 
20 
21 a = Test()
22 print(a.x)
23 a.x = '我穿上了马甲'
24 print(a.x)
25 del a.x
26 print(a.x)  # 输出:属性不存在

在这里插入图片描述
解决的问题:
✔️ 增加了异常捕获,在访问不存在的属性时可以不报AttributeError的错误。

三、property()函数的装饰器使用方法
    1. 实现
1 class Test:
2     def __init__(self):
3         self._text = '我不想让你看见'
4 
5 #  @property 修饰了text()方法,这样就使得该方法变成了text属性.
6 #   需要注意的是,如果类中只包含该方法,那么text属性将是一个只读属性
7     @property    # 替代get_t
8     def text(self):
9         try:
10             return self._text
11         except:
12             return '访问属性不存在'
13 
14     # 而要想实现修改text属性的值,还需要为text属性添加 setter 方法,
15     # 就需要用到 setter 装饰器,它的语法格式如下:
16     @text.setter    # # 替代set_t
17     def text(self, value):
18         self._text = value
19 
20     # 除此之外,还可以使用 deleter 装饰器来删除指定属性,其语法格式为:
21     @text.deleter    # 替代del_t
22     def text(self):
23         del self._text
24 
25 a = Test()
26 print(a.text)    # 无需括号,和属性完全一致
27 
28 a.text = '我又换马甲了'    # 直接赋值
29 print(a.text)
30 
31 del a.text    # 直接删除
32 print(a.text)

在这里插入图片描述
方法优势:
✔️ 每个操作对应一个装饰器
✔️ 方法名即属性名,更直观
✔️ 方便添加类型检查等逻辑

五、property()函数语法总结
  • 1.property()基本语法结构
    属性对象 = property(fget=None, fset=None, fdel=None, doc=None)

  • 2.参数说明
    在这里插入图片描述

    1. 返回值特性
      返回属性描述符对象
      自动绑定到类属性名
  • 4.装饰器用法

注意>定义顺序要求:
    1. 必须先声明 @property 方法
    1. setter/deleter 装饰器必须引用已存在的property方法
    1. 最终属性名由 @property 修饰的方法名决定
      正确用法示例:
1 class Demo:
2     @property
3     def attr(self):          # ① 定义getter
4         return self._value
5     
6     @attr.setter            # ② 定义setter(必须引用已存在的attr)
7     def attr(self, value):
8         self._value = value
9     
10     @attr.deleter           # ③ 定义deleter
11     def attr(self):
12         del self._value

在这里插入图片描述
错误用法示例:

1 class ErrorDemo:
2     @attr.setter  # ❌ 未先定义@property
3     def attr(self, value):
4         self._value = value
5 
6     @property
7     def attr(self):
8         return self._data
9 
10     @attr.deleter
11     def attr(self):
12         pass  # ❌ 方法名不一致

在这里插入图片描述

  • 顺序不可逆:必须按 @property → @xxx.setter → @xxx.deleter 顺序声明
  • 方法名统一:所有装饰方法必须使用相同的名称
  • 属性名锁定:最终属性名由 @property 修饰的方法名决定

装饰器

  • 1.装饰器的概念
    装饰器是一个函数,用于增强另一个函数的功能。通过高阶函数和闭包实现。
    主要功能:在不修改原函数代码的前提下增加新功能
  • 2.闭包机制回顾
    回顾案例:
1 def func1(a):
2     print(a)                    # ① 打印外层参数
3     print("函数 func1 正在执行")
4     def func2(b):
5         print(b)                # ③ 打印内层参数
6         print("函数 func2 正在执行")
7         return a + b            # ④ 访问外层变量
8     return func2                # ② 返回内层函数
9 
10 # 调用方式一(分步执行)
11 f = func1(1)    # 触发① → 返回func2
12 print(f)         # 输出:<function func1.<locals>.func2 at 0x...>
13 c = f(2)         # 触发③④ → 返回3
14 print(c)         # 输出:3
15 
16 # 调用方式二(链式调用)
17 c = func1(1)(2)  # 依次触发①③④
18 print(c)          # 输出:3

在这里插入图片描述

闭包与装饰器的关联:
  • 环境保留:func2持续访问func1的变量a
  • 函数工厂:func1生成不同配置的func2
  • 延迟执行:返回函数对象而非立即执行

3.使用装饰器和不使用装饰器的区别
通过计算函数运行时间来对比下
非装饰器写法:

1 import time
2 
3 def timer(func):
4     print('开始计时')        # ① 开始计时标记
5     start = time.time()    # ② 记录开始时间
6     func()                # ③ 执行原函数
7     end = time.time()     # ④ 记录结束时间
8     print('计时结束')      # ⑤ 结束计时标记
9     return f'一共花费了{end - start}秒的时间'  # ⑥ 返回计时结果
10 
11 def our_func():
12     n = 0
13     for i in range(1000001):
14         n += i
15     print('装饰器的计时工具')  # ⑦ 原函数执行标记
16     print(n)                # ⑧ 输出计算结果
17     return n
18 
19 # 调用方式
20 t = timer(our_func)  # 必须显式调用timer
21 print(t)             # 输出计时结果

在这里插入图片描述
装饰器用法:
装饰器的本质是一个闭包函数 🔻

1 import time
2 
3 def timer(func):  # ① 接受被装饰函数
4     def call():   # ② 定义包装函数
5         print('开始计时')  # 1
6         start = time.time()
7         n = func()         # ③ 执行原函数
8         end = time.time()
9         print('计时结束')  # 3
10         print(f'一共花费了{end - start}秒的时间')
11         return n           # ④ 返回原函数结果
12     return call            # ⑤ 返回包装函数
13 
14 @timer  # ⑥ 应用装饰器
15 def our_func():
16     n = 0
17     for i in range(1000001):
18         n += i
19     print('装饰器的计时工具')  # 2
20     return n
21 
22 
23 # 使用装饰器:相当于省略了这步>our_func = timer(our_func)
24 t = our_func()
25 print(t)

运行结果:
在这里插入图片描述
区别总结:

  1. 调用方式
  • 非装饰器:必须显式调用 timer(our_func),改变原函数调用方式
  • 装饰器:直接调用 our_func(),保持原函数调用方式
  1. 代码侵入性
  • 非装饰器:所有调用处需修改为 timer(our_func),牵一发而动全身
  • 装饰器:只需在函数定义处添加 @timer,无需修改调用代码
  1. 功能复用性
  • 非装饰器:需重复包裹逻辑,代码冗余
  • 装饰器:一次定义多处复用,代码简洁
  1. 设计原则
  • 非装饰器:违反开放封闭原则(修改原调用方式)
  • 装饰器:符合开放封闭原则(扩展而非修改)

4.语法糖
什么是语法糖?

  • 定义:通过语法特性简化代码,提升可读性和开发效率
  • 作用:隐藏底层实现细节,让代码更加简洁直观
  • 比如:@decorator 是 func = decorator(func)语法糖
    无语法糖写法:
1 def decor(func):
2     def name():
3         print("被装饰的函数 add 即将执行")
4         func()
5         print("被装饰的函数 add 已执行完毕")
6     return name
7 
8 def add():
9     print("函数 add 正在执行 ")
10 
11 # 手动包装函数
12 add = decor(add)  # 等价于 @decor
13 add()  # 调用包装后的函数

在这里插入图片描述
语法糖写法

1 def decor(func):
2     def name():
3         print("被装饰的函数 add 即将执行")
4         func()
5         print("被装饰的函数 add 已执行完毕")
6     return name
7 
8 @decor  # 语法糖应用
9 def add():
10     print("函数 add 正在执行 ")
11 
12 # 直接调用
13 add()  # 自动触发装饰器逻辑

在这里插入图片描述
区别总结:
1.装饰器调用方式

  • 无语法糖:需要手动调用装饰器函数,显式替换原函数
    也就是:add = decor(add)
  • 语法糖:通过 @decor 自动调用装饰器函数,隐式替换原函数
    也就是:
1 @decor
def add(): ...

2.代码简洁性

  • 无语法糖:代码分散,装饰器调用和函数定义分离
  • ​语法糖:代码集中,装饰器声明与函数定义在一起
    3.可读性
  • 无语法糖:需要额外理解 add = decor(add) 的替换逻辑
  • 语法糖:通过 @ 符号直观标识装饰器,逻辑更清晰
    4.维护性
  • 无语法糖:修改装饰器时需调整调用代码
  • 语法糖:修改装饰器时无需调整调用代码

5.为什么需要装饰器?

  • 传统实现案例:
  • 代码主体:
1 # 累加操作
2 def add(a):
3     start_time = time.time()  # 计时逻辑
4     total = 0
5     for i in range(0, a):
6         total += i
7     end_time = time.time()
8     exe_time = end_time - start_time
9     print(f'累加操作,花费的时间是{exe_time}')  # 计时逻辑
10     return total
11 
12 # 累乘操作
13 def mul(a):
14     start_time = time.time()  # 计时逻辑
15     product = 1
16     for i in range(1, a):
17         product *= i
18     end_time = time.time()
19     exe_time = end_time - start_time
20     print(f'累乘操作,花费的时间是{exe_time}')  # 计时逻辑
21     return product

代码调用:

1 # 累加的调用
2 total = add(100001)
3 print(total)    # 输出:5000050000
4 
5 # 累乘的调用
6 product = mul(100001)
7 print(product)    # 输出:累乘结果(非常大)

装饰器实现方法:
定义装饰器

1 import time
2 
3 def decorat(func):
4     def wrapper(a):  # 定义包装函数
5         start_time = time.time()
6         result = func(a)  # 执行原函数
7         end_time = time.time()
8         exe_time = end_time - start_time
9         print(f'该操作,花费的时间是{exe_time}')
10         return result  # 返回原函数结果
11     return wrapper

使用装饰器

1 @decorat
2 def add(a):
3     print('开始执行累加操作:')
4     total = 0
5     for i in range(0, a):
6         total += i
7     return total
8 
9 @decorat
10 def mul(a):
11     print('开始执行累乘操作:')
12     product = 1
13     for i in range(1, a):
14         product *= i
15     return product

代码调用

1 # 累加操作
2 f = add(100001)
3 print(f)    # 输出:5000050000
4 
5 # 累乘操作
6 g = mul(100001)
7 print(g)  # 输出:累乘结果(非常大)

生成器
1.什么是生成器?

  • 生成器是迭代器的一种实现方式。它通过 yield 关键字生成数据,每次调用生成器时,会返回一个值并暂停执行,直到下一次调用。

2.案例说明:依次打印 0-1000 的数字

  • 方式 1:使用函数生成(一次性保存到列表中)
1 def abc():
2     lyst = []
3     for i in range(1001):
4         lyst.append(i)
5     return lyst
6 
7 for i in abc():
8     print(i)

在这里插入图片描述

  • 方式 2:使用生成器(每次调用生成一个数据)
1 def gen():
2     for i in range(1001):
3         yield i
4 
5 for i in gen():
6     print(i)

在这里插入图片描述

  • 方式 3:使用生成器推导式
1 a = (i for i in range(1001))
2 for i in a:
3     print(i)

在这里插入图片描述

3.生成器的优势
  • 1.消耗内存少:处理大量数据时,只需要加载当前使用的数据,不需要一次性加载所有数据。
  • 2.语法简单:通过 yield 或推导式定义生成器,代码简洁易读。
4.生成器的第一种定义方式(函数式生成器)

生成器的第一种定义方式是通过 函数加 yield 关键字 来定义。
普通函数 vs 生成器

普通函数:
1 def intNum(n):
2     print("开始执行")
3     for i in range(n):
4         return i
5         print("继续执行")
6 
7 num = intNum(5)
8 print(num)  # 输出:0
9 num = intNum(5)
10 print(num)  # 输出:0
11 num = intNum(5)
12 print(num)  # 输出:0

问题:

  • 普通函数使用 return 后立即退出,无法生成多个值。
  • 每次调用函数都会重新执行,无法保留状态。
生成器:
1 def intNum(n):
2     print("开始执行")
3     for i in range(n):
4         yield i
5         print("继续执行")
6 
7 num = intNum(5)
8 print(next(num))  # 输出:开始执行 0
9 print(next(num))  # 输出:继续执行 1
10 print(next(num))  # 输出:继续执行 2
11 print(next(num))  # 输出:继续执行 3
12 print(next(num))  # 输出:继续执行 4
13 print(next(num))  # 抛出 StopIteration 异常

优势:

  • 生成器使用 yield,每次调用 next() 时生成一个值并暂停执行,保留函数状态。
  • 不需要一次性生成所有数据,节省内存。
    错误示例:
1 print(next(intNum(5)))  # 输出:开始执行 0
2 print(next(intNum(5)))  # 输出:开始执行 0
3 print(next(intNum(5)))  # 输出:开始执行 0

错误原因>>>每次调用 intNum(5) 都会创建一个新的生成器对象,无法保留状态。

while 循环定义生成器

1 def fib(max):
2     n = 0
3     while n < max:
4         yield n
5         n += 1
6 
7 a = fib(9)
8 print(next(a))  # 输出:0
9 print(next(a))  # 输出:1
10 print(next(a))  # 输出:2
11 print(next(a))  # 输出:3
12 print(next(a))  # 输出:4
13 print(next(a))  # 输出:5
14 print(next(a))  # 输出:6
15 print(next(a))  # 输出:7
16 print(next(a))  # 输出:8
17 print(next(a))  # 抛出 StopIteration 异常

在这里插入图片描述
说明:

  • 使用 while 循环定义生成器,可以更灵活地控制生成逻辑。
  • 每次调用 next() 时,生成器会执行到 yield 并返回当前值,然后暂停执行。
    案例:斐波那契数列
    通过while循环迭代器,实现斐波那契数列
    生成器主体
1 def fib(max):
2     a, b = 0, 1
3     while a < max:
4         yield a
5         print('---')
6         a, b = b, a + b
  • 使用生成器输出斐波那契数列
1 # 使用生成器输出斐波那契数列
2 a = fib(15)
3 print(next(a))  # 输出:0
4 print(next(a))  # 输出:1
5 print(next(a))  # 输出:1
6 print(next(a))  # 输出:2
7 print(next(a))  # 输出:3
8 print(next(a))  # 输出:5
9 print(next(a))  # 输出:8
10 print(next(a))  # 输出:13
11 print(next(a))  # 抛出 StopIteration 异常

在这里插入图片描述
说明:

  • 生成器 fib(max) 会生成斐波那契数列,直到值大于或等于 max。
  • 每次调用 next() 时,生成器会执行到 yield 并返回当前值,然后暂停执行。
  • 通过 a, b = b, a + b 更新数列的下一个值。
遍历生成器输出斐波那契数列
1 for n in fib(15):
2     print(n)

在这里插入图片描述

5.生成器的第二种定义方式(生成器表达式)

  • 这种生成器定义方式是 使用推导式。与列表推导式类似,生成器推导式使用圆括号 () 而不是方括号 [],而且它返回的是一个生成器对象,不是列表。
1.基本语法:
  • g = (expression for item in iterable)
  • expression:生成器每次生成的值的表达式。
  • item:迭代变量,从 iterable 中取值。
  • iterable:可迭代对象(如列表、范围等)。
2.基本示例:
1 g = (i for i in range(100))
2 print(g)  # 输出:<generator object <genexpr> at 0x...>
3 
4 # 遍历生成器
5 for i in g:
6     print(i)
  • g 是一个生成器对象
  • 需要 遍历 g(或使用next(g)),查看生成器的值。
3.加条件筛选:
  • 生成器推导式可以添加条件筛选,只生成符合条件的值。
1 # 只生成能被 3 整除的数
2 g = (i for i in range(100) if i % 3 == 0)
3 print(g)  # 输出:<generator object <genexpr> at 0x...>
4 
5 # 遍历生成器
6 for i in g:
7     print(i)
4.带条件判断的生成器推导式
  • 生成器推导式可以结合条件判断,生成不同的值。
1 # 小于 10 的数直接输出,否则输出 '哈哈'
2 g = (i if i < 10 else '哈哈' for i in range(100))
3 print(g)  # 输出:<generator object <genexpr> at 0x...>
4 
5 # 遍历生成器
6 for i in g:
7     print(i)
5.对比列表推导式

生成器推导式与列表推导式的语法非常相似,但它们的返回值不同:

  • 生成器推导式返回生成器对象。
  • 列表推导式返回列表对象。
    例如:
1 # 生成器推导式
2 g = (i if i < 10 else '哈哈' for i in range(100))
3 print("返回生成器对象:", g)  # 输出:<generator object <genexpr> at 0x...>
4 
5 # 列表推导式
6 g_list = [i if i < 10 else '哈哈' for i in range(100)]
7 print("返回列表对象:", g_list)  # 输出:[0, 1, 2, ..., 9, '哈哈', '哈哈', ...]

在这里插入图片描述
图片

🟢 总结

  • 最后希望你编程学习上不急不躁,按照计划有条不紊推进,把任何一件事做到极致,都是不容易的,加油,努力!相信自己!

🟡 文末福利

  • 最后这里免费分享给大家一份Python全套学习资料,希望能帮到那些不满现状,想提升自己却又没有方向的朋友,也可以和我一起来学习交流呀。
🔴包含编程资料、学习路线图、源代码、软件安装包等!【点击这里】领取!
  • ① Python所有方向的学习路线图,清楚各个方向要学什么东西
  • ② 100多节Python课程视频,涵盖必备基础、爬虫和数据分析
  • ③ 100多个Python实战案例,学习不再是只会理论
  • ④ 华为出品独家Python漫画教程,手机也能学习

可以扫描下方二维码领取【保证100%免费

在这里插入图片描述

Logo

葡萄城是专业的软件开发技术和低代码平台提供商,聚焦软件开发技术,以“赋能开发者”为使命,致力于通过表格控件、低代码和BI等各类软件开发工具和服务

更多推荐