29类和对象---封装
1、通过双下划线设置成私有,但是依然可以访问
class A: __N = 2 #类的数据属性应该就是共享的,但也可以设为私有的,此处变形为_A__N=2 def __init__(self): self.__X = 10 #变形为_A__X=10 def __foo(self): #变形为_A__foo print('from A') def bar(self): self.__foo()#只有在类的内部才可以通过.__foo()调用 a = A() a.bar() # 在类的外部是不能通过a.__foo()访问的,要通过_类名__属性访问 print(a._A__X) a._A__foo() print('dict1',a.__dict__)#dict1 {'_A__X': 10} a.__hh = 'a' print('dict2',a.__dict__)#dict2 {'_A__X': 10, '__hh': 'a'} # print(a.__dict__) a.__H = 9 # 变形只会发生在类定义的时候,之后的赋值不会变化了{'__H': 9, '_A__X': 10} print(a.__dict__)
2、父类如果不想让子类覆盖自己的方法,可以将方法定义为私有
class A: def __fa(self): # 在定义变形为_A__fa print('__fa form A') def test2(self): self.__fa() # 只会与自己所在的类为准,即调用_A__fa class B(A): def __fa(self): print('__fa from B') b = B() b.test1() #__fa form A,如果不设置为私有,那么调用__fa()时会调用class B 内的方法,打印__fa from B b._B__fa() #__fa from B
3、封装的意义,
封装的真谛在于明确地区分内外,封装的属性可以直接在内部使用,而不能被外部直接使用,
然而定义属性的目的终归是要用,外部要想用类隐藏的属性,需要我们为其开辟接口,
让外部能够间接地用到我们隐藏起来的属性
python并不会真的阻止你访问私有属性,模块也是如此,如果要严格的控制属性的访问权限,要借助内置方法如__getattr__
(1)封装数据:
将数据隐藏起来这不是目的,将数据隐藏起来并提供对外操作的接口,
可以在这个接口上附加上对该数据的限制,以此完成对数据属性操作的严格控制。
class Teacher: def __init__(self, name, age):# 将name age隐藏起来 # self.name = name # self.age = age self.set_info(name,age) def tell_info(self):#对外提供访问属性的接口 print('姓名:{},年龄:{}'.format(self.__name,self.__age)) def set_info(self, name, age):#对外提供设置属性的接口,并附加类型检测 if not isinstance(name, str): raise TypeError('姓名必须是字符串') if not isinstance(age, int): raise TypeError('年龄必须是数字') self.__name = name self.__age = age t = Teacher('cc',12) t.tell_info() t2= Teacher('cc','12')#报错,TypeError: 年龄必须是数字
(2)封装方法:
隔离复杂度,将一些细节方法封装,并提供一个接口函数,
使用者只需知道接口函数的功能即可,至于内部的实现细节则没必要了解
#取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱 #对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来, # 很明显这么做隔离了复杂度,同时也提升了安全性 class ATM: def __card(self): print('插卡') def __auth(self): print('用户认证') def __input(self): print('输出密码') def __take_money(self): print('取钱') def do(self): self.__card() self.__auth() self.__input() self.__take_money() a = ATM() a.do()#隐藏内部的细节
4、特性 property
装饰器,后面会自己写出来,但现在已经完全忘记了,真的想不起来了。。。。
import math class Cirlce: def __init__(self, radius): self.radius = radius @property def area(self): return math.pi * self.radius**2 @property def perimeter(self): return 2 * math.pi * self.radius c = Cirlce(3) # 封装函数,像是一个属性 print(c.area) print(c.perimeter)
面向对象的封装有三种方式:public、protected、private
python并没有在语法上把它们三个内建到自己的class机制中,在C++里一般会将所有的所有的数据都设置为私有的,
然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现
class Foo: def __init__(self,val): self.__NAME=val #将所有的数据属性都隐藏起来 @property def name(self): return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置) @name.setter def name(self,value): if not isinstance(value,str): #在设定值之前进行类型检查 raise TypeError('%s must be str' %value) self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME @name.deleter def name(self): raise TypeError('Can not delete') f=Foo('egon') print(f.name) f.name=10 #抛出异常'TypeError: 10 must be str' del f.name #抛出异常'TypeError: Can not delete'
class Person: def __init__(self,name): self._name = name #_使用__name把name属性封装起来 # 封装后外部不能访问name属性,这时对外开一个接口,可以得到name属性,比如定义一个get_name()方法 # 但name确实是一个数据属性,用get_name()不好,这时候可以利用property装饰器 # 将获取name方法转换为获取对象的属性,虽然调用的name()方法,但用propery装饰后,在外部看来就是调用的数据属性 @property def name(self): return self._name # 前面的代码只是可以得到name属性,因为name封装的原因,不能进行赋值,比如p.name='cc' # 这时候再定义一个可以赋值的函数,使用@name.setter装饰,就可以对封装的name属性进行赋值了 @name.setter def name(self,name): if type(name) is not str:#这里可以加对属性的判断条件 raise TypeError('必须是str') self._name = name p = Person('小黑') print(p.name) # 本质是调用name()方法,但用propery装饰后,在外部看来就是调用的数据属性 p.name = '小灰' # name是封装的属性,但用@name.setter装饰后可以像正常的属性一样进行赋值 print(p.name)
class Person: def __init__(self,name): self._name = name @property def name(self): a=100 return self._name,a @name.setter def name(self,name): # name是一个列表,包含2个元素 self._name = name[0] # 第一个元素值赋给self._name a=name[1] # 第二个值赋给a了,可是在执行上一个name()函数时候会有a=100,因此才不会改变变量的 p = Person('小黑') print(p.name) # ('小黑', 100) p.name = ['小白',99] # 因为执行了a=100,所以执行a=name[1]时,也不会改变输出值 print(p.name) #('小白', 100)
5、封装与扩展性,只要接口约定不变,那么则可以改变内部代码的实现,但外部调用感知不到
类的设计者 class Room: def __init__(self,name,owner,width,length,high): self.name=name self.owner=owner self.__width=width self.__length=length self.__high=high def tell_area(self): #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积 return self.__width * self.__length #使用者 >>> r1=Room('卧室','egon',20,20,20) >>> r1.tell_area() #使用者调用接口tell_area #类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码 class Room: def __init__(self,name,owner,width,length,high): self.name=name self.owner=owner self.__width=width self.__length=length self.__high=high def tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了 return self.__width * self.__length * self.__high #对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能 >>> r1.tell_area()
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】