✅包含编程资料、学习路线图、源代码、软件安装包等!【[点击这里]】!
属性控制
一、基础方法属性化
- 传统方法控制
1# 1. 传统方法控制2classTest:3def__init__(self):4self._text='我不想让你看见'56# 显式操作接口7defget_t(self):8returnself._text910defset_t(self,value):11self._text=value1213defdel_t(self):14delself._text151617# 使用示例18a=Test()19print(a.get_t())# 调用并查看属性20print(a._text)# 也可以直接调用属性21a.set_t('新值')# 显式调用方法修改属性值22print(a.get_t())# 输出:新值23a.del_t()# 删除属性24print(a.get_t())# 删除后调用,报错:AttributeError: 'Test' object has no attribute '_text'传统方法的问题;
✔️ 每次操作都要记方法名
✔️ 可以直接访问a._text不安全
✔️ 代码不够简洁直观
- 为了能简化这些操作步骤,我们可以选择 property()函数来替代那些繁琐的步骤。
二、property()函数进阶
- 属性绑定方法
1classTest:2""" 3 保持原有方法不变 4 """5def__init__(self):6self._text='我不想让你看见'78defget_t(self):9returnself._text1011defset_t(self,value):12self._text=value1314defdel_t(self):15delself._text1617# 把三个方法打包成属性18x=property(get_t,set_t,del_t)192021# 实例化对象22a=Test()23""" 24 a.x 将触发 getter方法, 25 a.x = value 将触发 setter方法, 26 del a.x 触发 deleter方法. 27 """28print(a.x)# 隐式调用get_t → 输出:我不想让你看见29a.x='马甲属性'# 隐式调用set_t(value),设置属性30print(a.x)# 隐式调用get_t → 输出:马甲属性31dela.x# 隐式调用del_t 删除属性32print(a.x)# 调用已删除的属性
解决的问题:
✔️ 用属性语法替代方法调用
✔️ 隐藏真实属性名_text
✔️ 保持数据验证能力(可在方法中添加检查)
- 2.加一层异常捕获:健壮性改造
1classTest:2def__init__(self):3self._text='我不想让你看见'45# 带异常处理的访问器6defget_t(self):7try:8returnself._text9except:10return'属性不存在'1112defset_t(self,value):13self._text=value1415defdel_t(self):16delself._text1718x=property(get_t,set_t,del_t)192021a=Test()22print(a.x)23a.x='我穿上了马甲'24print(a.x)25dela.x26print(a.x)# 输出:属性不存在
解决的问题:
✔️ 增加了异常捕获,在访问不存在的属性时可以不报AttributeError的错误。
三、property()函数的装饰器使用方法
- 实现
1classTest:2def__init__(self):3self._text='我不想让你看见'45# @property 修饰了text()方法,这样就使得该方法变成了text属性.6# 需要注意的是,如果类中只包含该方法,那么text属性将是一个只读属性7@property# 替代get_t8deftext(self):9try:10returnself._text11except:12return'访问属性不存在'1314# 而要想实现修改text属性的值,还需要为text属性添加 setter 方法,15# 就需要用到 setter 装饰器,它的语法格式如下:16@text.setter# # 替代set_t17deftext(self,value):18self._text=value1920# 除此之外,还可以使用 deleter 装饰器来删除指定属性,其语法格式为:21@text.deleter# 替代del_t22deftext(self):23delself._text2425a=Test()26print(a.text)# 无需括号,和属性完全一致2728a.text='我又换马甲了'# 直接赋值29print(a.text)3031dela.text# 直接删除32print(a.text)
方法优势:
✔️ 每个操作对应一个装饰器
✔️ 方法名即属性名,更直观
✔️ 方便添加类型检查等逻辑
五、property()函数语法总结
1.property()基本语法结构
属性对象 = property(fget=None, fset=None, fdel=None, doc=None)2.参数说明
- 返回值特性
返回属性描述符对象
自动绑定到类属性名
- 返回值特性
4.装饰器用法
注意>定义顺序要求:
- 必须先声明 @property 方法
- setter/deleter 装饰器必须引用已存在的property方法
- 最终属性名由 @property 修饰的方法名决定
正确用法示例:
- 最终属性名由 @property 修饰的方法名决定
1classDemo:2@property3defattr(self):# ① 定义getter4returnself._value56@attr.setter# ② 定义setter(必须引用已存在的attr)7defattr(self,value):8self._value=value910@attr.deleter# ③ 定义deleter11defattr(self):12delself._value
错误用法示例:
1classErrorDemo:2@attr.setter# ❌ 未先定义@property3defattr(self,value):4self._value=value56@property7defattr(self):8returnself._data910@attr.deleter11defattr(self):12pass# ❌ 方法名不一致- 顺序不可逆:必须按
@property → @xxx.setter → @xxx.deleter顺序声明 - 方法名统一:所有装饰方法必须使用相同的名称
- 属性名锁定:最终属性名由
@property修饰的方法名决定
装饰器
- 1.装饰器的概念
装饰器是一个函数,用于增强另一个函数的功能。通过高阶函数和闭包实现。
主要功能:在不修改原函数代码的前提下增加新功能 - 2.闭包机制回顾
回顾案例:
1deffunc1(a):2print(a)# ① 打印外层参数3print("函数 func1 正在执行")4deffunc2(b):5print(b)# ③ 打印内层参数6print("函数 func2 正在执行")7returna+b# ④ 访问外层变量8returnfunc2# ② 返回内层函数910# 调用方式一(分步执行)11f=func1(1)# 触发① → 返回func212print(f)# 输出:<function func1.<locals>.func2 at 0x...>13c=f(2)# 触发③④ → 返回314print(c)# 输出:31516# 调用方式二(链式调用)17c=func1(1)(2)# 依次触发①③④18print(c)# 输出:3闭包与装饰器的关联:
- 环境保留:func2持续访问func1的变量a
- 函数工厂:func1生成不同配置的func2
- 延迟执行:返回函数对象而非立即执行
3.使用装饰器和不使用装饰器的区别
通过计算函数运行时间来对比下
非装饰器写法:
1importtime23deftimer(func):4print('开始计时')# ① 开始计时标记5start=time.time()# ② 记录开始时间6func()# ③ 执行原函数7end=time.time()# ④ 记录结束时间8print('计时结束')# ⑤ 结束计时标记9returnf'一共花费了{end-start}秒的时间'# ⑥ 返回计时结果1011defour_func():12n=013foriinrange(1000001):14n+=i15print('装饰器的计时工具')# ⑦ 原函数执行标记16print(n)# ⑧ 输出计算结果17returnn1819# 调用方式20t=timer(our_func)# 必须显式调用timer21print(t)# 输出计时结果
装饰器用法:
装饰器的本质是一个闭包函数 🔻
1importtime23deftimer(func):# ① 接受被装饰函数4defcall():# ② 定义包装函数5print('开始计时')# 16start=time.time()7n=func()# ③ 执行原函数8end=time.time()9print('计时结束')# 310print(f'一共花费了{end-start}秒的时间')11returnn# ④ 返回原函数结果12returncall# ⑤ 返回包装函数1314@timer# ⑥ 应用装饰器15defour_func():16n=017foriinrange(1000001):18n+=i19print('装饰器的计时工具')# 220returnn212223# 使用装饰器:相当于省略了这步>our_func = timer(our_func)24t=our_func()25print(t)运行结果:
区别总结:
- 调用方式
- 非装饰器:必须显式调用 timer(our_func),改变原函数调用方式
- 装饰器:直接调用 our_func(),保持原函数调用方式
- 代码侵入性
- 非装饰器:所有调用处需修改为 timer(our_func),牵一发而动全身
- 装饰器:只需在函数定义处添加 @timer,无需修改调用代码
- 功能复用性
- 非装饰器:需重复包裹逻辑,代码冗余
- 装饰器:一次定义多处复用,代码简洁
- 设计原则
- 非装饰器:违反开放封闭原则(修改原调用方式)
- 装饰器:符合开放封闭原则(扩展而非修改)
4.语法糖
什么是语法糖?
- 定义:通过语法特性简化代码,提升可读性和开发效率
- 作用:隐藏底层实现细节,让代码更加简洁直观
- 比如:@decorator 是 func = decorator(func)语法糖
无语法糖写法:
1defdecor(func):2defname():3print("被装饰的函数 add 即将执行")4func()5print("被装饰的函数 add 已执行完毕")6returnname78defadd():9print("函数 add 正在执行 ")1011# 手动包装函数12add=decor(add)# 等价于 @decor13add()# 调用包装后的函数
语法糖写法
1defdecor(func):2defname():3print("被装饰的函数 add 即将执行")4func()5print("被装饰的函数 add 已执行完毕")6returnname78@decor# 语法糖应用9defadd():10print("函数 add 正在执行 ")1112# 直接调用13add()# 自动触发装饰器逻辑
区别总结:
1.装饰器调用方式
- 无语法糖:需要手动调用装饰器函数,显式替换原函数
也就是:add = decor(add) - 语法糖:通过 @decor 自动调用装饰器函数,隐式替换原函数
也就是:
1@decordefadd():...2.代码简洁性
- 无语法糖:代码分散,装饰器调用和函数定义分离
- 语法糖:代码集中,装饰器声明与函数定义在一起
3.可读性 - 无语法糖:需要额外理解 add = decor(add) 的替换逻辑
- 语法糖:通过 @ 符号直观标识装饰器,逻辑更清晰
4.维护性 - 无语法糖:修改装饰器时需调整调用代码
- 语法糖:修改装饰器时无需调整调用代码
5.为什么需要装饰器?
- 传统实现案例:
- 代码主体:
1# 累加操作2defadd(a):3start_time=time.time()# 计时逻辑4total=05foriinrange(0,a):6total+=i7end_time=time.time()8exe_time=end_time-start_time9print(f'累加操作,花费的时间是{exe_time}')# 计时逻辑10returntotal1112# 累乘操作13defmul(a):14start_time=time.time()# 计时逻辑15product=116foriinrange(1,a):17product*=i18end_time=time.time()19exe_time=end_time-start_time20print(f'累乘操作,花费的时间是{exe_time}')# 计时逻辑21returnproduct代码调用:
1# 累加的调用2total=add(100001)3print(total)# 输出:500005000045# 累乘的调用6product=mul(100001)7print(product)# 输出:累乘结果(非常大)装饰器实现方法:
定义装饰器
1importtime23defdecorat(func):4defwrapper(a):# 定义包装函数5start_time=time.time()6result=func(a)# 执行原函数7end_time=time.time()8exe_time=end_time-start_time9print(f'该操作,花费的时间是{exe_time}')10returnresult# 返回原函数结果11returnwrapper使用装饰器
1@decorat2defadd(a):3print('开始执行累加操作:')4total=05foriinrange(0,a):6total+=i7returntotal89@decorat10defmul(a):11print('开始执行累乘操作:')12product=113foriinrange(1,a):14product*=i15returnproduct代码调用
1# 累加操作2f=add(100001)3print(f)# 输出:500005000045# 累乘操作6g=mul(100001)7print(g)# 输出:累乘结果(非常大)生成器
1.什么是生成器?
- 生成器是迭代器的一种实现方式。它通过
yield关键字生成数据,每次调用生成器时,会返回一个值并暂停执行,直到下一次调用。
2.案例说明:依次打印 0-1000 的数字
- 方式 1:使用函数生成(一次性保存到列表中)
1defabc():2lyst=[]3foriinrange(1001):4lyst.append(i)5returnlyst67foriinabc():8print(i)- 方式 2:使用生成器(每次调用生成一个数据)
1defgen():2foriinrange(1001):3yieldi45foriingen():6print(i)- 方式 3:使用生成器推导式
1a=(iforiinrange(1001))2foriina:3print(i)3.生成器的优势
- 1.消耗内存少:处理大量数据时,只需要加载当前使用的数据,不需要一次性加载所有数据。
- 2.语法简单:通过 yield 或推导式定义生成器,代码简洁易读。
4.生成器的第一种定义方式(函数式生成器)
生成器的第一种定义方式是通过 函数加 yield 关键字 来定义。
普通函数 vs 生成器
普通函数:
1defintNum(n):2print("开始执行")3foriinrange(n):4returni5print("继续执行")67num=intNum(5)8print(num)# 输出:09num=intNum(5)10print(num)# 输出:011num=intNum(5)12print(num)# 输出:0问题:
- 普通函数使用 return 后立即退出,无法生成多个值。
- 每次调用函数都会重新执行,无法保留状态。
生成器:
1defintNum(n):2print("开始执行")3foriinrange(n):4yieldi5print("继续执行")67num=intNum(5)8print(next(num))# 输出:开始执行 09print(next(num))# 输出:继续执行 110print(next(num))# 输出:继续执行 211print(next(num))# 输出:继续执行 312print(next(num))# 输出:继续执行 413print(next(num))# 抛出 StopIteration 异常优势:
- 生成器使用 yield,每次调用 next() 时生成一个值并暂停执行,保留函数状态。
- 不需要一次性生成所有数据,节省内存。
错误示例:
1print(next(intNum(5)))# 输出:开始执行 02print(next(intNum(5)))# 输出:开始执行 03print(next(intNum(5)))# 输出:开始执行 0错误原因>>>每次调用 intNum(5) 都会创建一个新的生成器对象,无法保留状态。
while 循环定义生成器
1deffib(max):2n=03whilen<max:4yieldn5n+=167a=fib(9)8print(next(a))# 输出:09print(next(a))# 输出:110print(next(a))# 输出:211print(next(a))# 输出:312print(next(a))# 输出:413print(next(a))# 输出:514print(next(a))# 输出:615print(next(a))# 输出:716print(next(a))# 输出:817print(next(a))# 抛出 StopIteration 异常
说明:
- 使用 while 循环定义生成器,可以更灵活地控制生成逻辑。
- 每次调用 next() 时,生成器会执行到 yield 并返回当前值,然后暂停执行。
案例:斐波那契数列
通过while循环迭代器,实现斐波那契数列
生成器主体
1deffib(max):2a,b=0,13whilea<max:4yielda5print('---')6a,b=b,a+b- 使用生成器输出斐波那契数列
1# 使用生成器输出斐波那契数列2a=fib(15)3print(next(a))# 输出:04print(next(a))# 输出:15print(next(a))# 输出:16print(next(a))# 输出:27print(next(a))# 输出:38print(next(a))# 输出:59print(next(a))# 输出:810print(next(a))# 输出:1311print(next(a))# 抛出 StopIteration 异常
说明:
- 生成器 fib(max) 会生成斐波那契数列,直到值大于或等于 max。
- 每次调用 next() 时,生成器会执行到 yield 并返回当前值,然后暂停执行。
- 通过 a, b = b, a + b 更新数列的下一个值。
遍历生成器输出斐波那契数列
1forninfib(15):2print(n)5.生成器的第二种定义方式(生成器表达式)
- 这种生成器定义方式是 使用推导式。与列表推导式类似,生成器推导式使用圆括号 () 而不是方括号 [],而且它返回的是一个生成器对象,不是列表。
1.基本语法:
- g = (expression for item in iterable)
- expression:生成器每次生成的值的表达式。
- item:迭代变量,从 iterable 中取值。
- iterable:可迭代对象(如列表、范围等)。
2.基本示例:
1g=(iforiinrange(100))2print(g)# 输出:<generator object <genexpr> at 0x...>34# 遍历生成器5foriing:6print(i)- g 是一个生成器对象
- 需要 遍历 g(或使用next(g)),查看生成器的值。
3.加条件筛选:
- 生成器推导式可以添加条件筛选,只生成符合条件的值。
1# 只生成能被 3 整除的数2g=(iforiinrange(100)ifi%3==0)3print(g)# 输出:<generator object <genexpr> at 0x...>45# 遍历生成器6foriing:7print(i)4.带条件判断的生成器推导式
- 生成器推导式可以结合条件判断,生成不同的值。
1# 小于 10 的数直接输出,否则输出 '哈哈'2g=(iifi<10else'哈哈'foriinrange(100))3print(g)# 输出:<generator object <genexpr> at 0x...>45# 遍历生成器6foriing:7print(i)5.对比列表推导式
生成器推导式与列表推导式的语法非常相似,但它们的返回值不同:
- 生成器推导式返回生成器对象。
- 列表推导式返回列表对象。
例如:
1# 生成器推导式2g=(iifi<10else'哈哈'foriinrange(100))3print("返回生成器对象:",g)# 输出:<generator object <genexpr> at 0x...>45# 列表推导式6g_list=[iifi<10else'哈哈'foriinrange(100)]7print("返回列表对象:",g_list)# 输出:[0, 1, 2, ..., 9, '哈哈', '哈哈', ...]🟢总结
- 最后希望你编程学习上不急不躁,按照计划有条不紊推进,把任何一件事做到极致,都是不容易的,加油,努力!相信自己!
🟡文末福利
- 最后这里免费分享给大家一份Python全套学习资料,希望能帮到那些不满现状,想提升自己却又没有方向的朋友,也可以和我一起来学习交流呀。
🔴包含编程资料、学习路线图、源代码、软件安装包等!【点击这里】领取!
- ① Python所有方向的学习路线图,清楚各个方向要学什么东西
- ② 100多节Python课程视频,涵盖必备基础、爬虫和数据分析
- ③ 100多个Python实战案例,学习不再是只会理论
- ④ 华为出品独家Python漫画教程,手机也能学习