5 [面向对象]-三大特性:封装
1、引子
从封装本身的意思去理解,封装就好像是拿来一个麻袋,把小猫,小狗,小王八,还有alex一起装进麻袋,然后把麻袋封上口子。照这种逻辑看,封装=‘隐藏’,这种理解是相当片面的
先看如何隐藏
在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)
class A: __x = 100 # 类的私有数据属性 y = 200 def __init__(self, name): self.__name = name # 对象的私有属性 def __foo(self): # 类的私有方法 print('run foo')
print(A.y) # print(A.__x) # 私有数据属性 # '_A__x': 100, # print(A.__foo) # {'_A__foo': <function A.__foo at 0x01FC17C8>, print(A.__dict__) print(A._A__x) print(A._A__foo) a = A('alex') print(a.__dict__) # print(a.__name) # {'_A__name': 'alex'} print(a._A__name)
### 运行结果 200 {'__weakref__': <attribute '__weakref__' of 'A' objects>, '__module__': '__main__', '__init__': <function A.__init__ at 0x01F21738>, '_A__x': 100, '_A__foo': <function A.__foo at 0x01F217C8>, '__dict__': <attribute '__dict__' of 'A' objects>, '__doc__': None, 'y': 200} 100 <function A.__foo at 0x01F217C8> {'_A__name': 'alex'} alex
2、私有属性,方法的实质
#其实这仅仅这是一种变形操作 #类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式: class A: __N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N def __init__(self): self.__X=10 #变形为self._A__X def __foo(self): #变形为_A__foo print('from A') def bar(self): self.__foo() #只有在类内部才可以通过__foo的形式访问到. #A._A__N是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形
3、特点
class A: __x = 100 # 类的私有数据属性 y = 200 def __init__(self, name): self.__name = name # 对象的私有属性 def __foo(self): # 类的私有方法 print('run foo') def bar(self): # 内部可以调用私有属性,方法 self.__foo() # self._A__foo print(self.__name) print(A.__x) a = A('alex') a.bar()
## 运行结果 run foo alex 100
(2)子类无法覆盖父类的__开头的属性,方法
class Foo: def func(self): # _Foo_func print('run foo') class Bar(Foo): def func(self): # _Bar_func print('new from bar') b = Bar() b.func() # 子类覆盖了父类的方法
- 外部不可以调用私有属性,方法
class Foo: def __func(self): # _Foo_func print('run foo') class Bar(Foo): def __func(self): # _Bar_func print('new from bar') b = Bar() b.func() # 子类覆盖了父类的方法
4、注意的问题
这种变形需要注意的问题是:
1、这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
2、变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形
3、在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
5、封装的意义
1).封装数据属性,明确的区分内外
class People: def __init__(self, name, age): self.__name = name self.__age = age def tell_info(self): print('%s - %s'%(self.__name, self.__age)) def set_info(self, name, age): self.__name = name self.__age = age p = People('alex', 14) # print(p.__name) p.tell_info() p.set_info('jack', 33) p.tell_info()
将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制
class People: def __init__(self, name, age): self.__name = name self.__age = age def tell_info(self): print('%s - %s'%(self.__name, self.__age)) def set_info(self, name, age): if not isinstance(name, str): print('名字必须是字符串类型') return if not isinstance(age, int): print('年龄必须是数字类型') return self.__name = name self.__age = age p = People('alex', 14) # print(p.__name) p.tell_info() p.set_info(11, '33') p.tell_info()
2):封装方法:目的是隔离复杂度
#取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱 #对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做 #隔离了复杂度,同时也提升了安全性 class ATM: def __card(self): print('插卡') def __auth(self): print('用户认证') def __input(self): print('输入取款金额') def __print_bill(self): print('打印账单') def __take_money(self): print('取款') def withdraw(self): self.__card() self.__auth() self.__input() self.__print_bill() self.__take_money() a=ATM() a.withdraw()
6、封装与扩展性
class Room: def __init__(self, name, owner, weight, length): self.name = name self.owner = owner self.__weight = weight self.__length = length def tell_area(self): return self.__weight * self.__length r = Room('卫生间', 'alex', 80, 80) print(r.tell_area())