008---封装
封装
引子
从封装本身的字面意思理解就是把一大堆事物用麻袋封装起来,达到隐藏的效果,其实这种理解是片面的。
-
广义上的封装:
- 把共有的属性和方法封装到类中:类属性和绑定方法
- 把独有的属性封装到对象中:
__init__()
-
狭义上的封装
- 私有化,只能在类的内部访问
- 单下划线(_name):保护变量,只有类对象和子类对象本身能访问到这些变量。不能用于from...import ...
- 双下划线(__name):私有属性,只有类对象自己能访问,连子类对象也不能访问到这个数据。
class A: # 会变形为:_A__x,并不是真正意义外部不能访问,仅仅只是一种语法上的变形。 __x = 1 _y = 2 def __init__(self, name): self.__name = name # 变形为_A__foo def __foo(self): return 'foo已运行' def bar(self): print(self.__foo()) return 'bar已运行' # 类访问私有成员 print('验证变形之后的__x:', A.__dict__.get('_A__x')) print('验证保护属性_y是否变形:', A.__dict__.get('_y')) print('验证变形之后的__foo:', A.__dict__.get('_A__foo')) print('外部类访问私有属性:', A._A__x) print('外部类执行私有方法:', A._A__foo(1)) print('外部类访问保护属性:', A._y) # 对象访问私有成员 a = A('alex') print('外部对象访问私有属性:', a._A__name) print('外部对象访问私有属性:', a._A__x) print('外部对象访问保护属性:', a._y) print('外部对象执行私有方法:', a._A__foo()) print('外部对象间接访问私有方法:', a.bar()) ''' 这种变形的特点: 1、外部无法直接obj.__AttrName 2、在类的内部obj.__AttrName可调用,定义阶段 3、子类中无法覆盖__开头的属性,也就是说子类不可以访问。 保护属性虽然没有变形。但也请把它视为私有属性 '''
- 私有化,只能在类的内部访问
-
在继承中,父类如果不想子类覆盖自己的方法。可以将其定义为私有的。
class C:
def __foo(self): # _C.__foo
print('C.foo')
def bar(self):
print('A.bar')
# 如果需求是调用父类的foo,就可以使用封装
self.__foo()
class D(C):
def __foo(self): # _D.__foo
print('D.foo')
d = D()
d.bar()
封装的意义
- 明确的区分内外,控制外部对内部私有成员的操作行为
- 隔离复杂度
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# __author__ = "ziya"
# Date: 2018-08-26
# 一、封装数据属性:明确的区分内外,控制外部对隐藏的属性的操作行为
class People:
def __init__(self, name, age):
self.__name = name
self.__age = age
def tell_info(self):
print('Name:%s> Age:%s' % (self.__name, self.__age))
def set_info(self, name, age):
if not isinstance(name, str):
return
if not isinstance(age, int):
return
self.__name = name
self.__age = age
def __func1(self):
print(111)
p = People('engo', 18)
# 提供接口供外部访问
p.tell_info()
# 表面上修改了name的值,其实不是,只是增加了一个name的属性。因为原来的变形了。。。
p.name = 1
print(p.__dict__)
# 二、封装方法的目的,隔离复杂度
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()
'''
插卡
用户认证
输入取款金额
打印账单
取款
'''
'''
取款是功能,而这个功能有很多小功能组成,
对使用者来说,只要知道取款这个功能即可。其余功能隐藏起来。很明显隔离了复杂度,同时也提升了安全性
'''
特性:property
- 什么是特性property
property是一种特殊的属性,访问它时会执行一端功能(函数),然后返回值。调用时,像调用属性一样。
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# __author__ = "ziya"
# Date: 2018-08-27
'''
property是一种特殊的属性,访问它时会执行一段功能,然后返回值。调用时,直接像调属性一样
'''
'''
例一:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)
成人的BMI数值:
过轻:低于18.5
正常:18.5-23.9
过重:24-27
肥胖:28-32
非常肥胖, 高于32
体质指数(BMI)=体重(kg)÷身高^2(m)
EX:70kg÷(1.75×1.75)=22.86
'''
class People:
def __init__(self, name, weight, height):
self.name = name
self.weight = weight
self.height = height
# 表面效果一样,其实不一样,如果实例化对象后,修改了对象的属性。bmi初始化的时候已经定型了,后面不会重新计算
self.bmi1 = self.weight / (self.height ** 2)
@property
def bmi(self):
return self.weight / (self.height ** 2)
p = People('jw', 60, 1.70)
print('通过property动态得到bim属性:', p.bmi)
print('通过初始化方法计算得到bim属性:', p.bmi1)
p.weight = 120
print('修改后', p.bmi)
print('修改后', p.bmi1)
class Person:
def __init__(self, name):
self.__name = name
@property
def name(self):
return self.__name
@name.setter
def name(self, val):
if not isinstance(val, str):
print('名字必须时字符串类型')
return
self.__name = val
@name.deleter
def name(self):
print('不允许删除')
p = Person('江子牙')
print(p.name)
# 尝试设置name的值,提示必须为字符串类型
p.name = 1
print(p.name)
# 尝试删除name的值,提示不允许删除
del p.name # 触发deleter 不允许删除
- 为什么要使用特性:遵循了统一访问的原则。
封装与拓展性
class Room:
def __init__(self,name,owner,width,length,height):
self.name = name
self.owner = owner
self.__width = width
self.__length = length
self.__height = height
def area(self):
# 求体积
# return self.__width * self.__length * self.__height
# 求面积
return self.__width * self.__length
r = Room('卫生间','alex',10,10,8)
# 调用接口获取房子体积,而不用管内部怎么实现。
print(r.area())
- 对于类实现者可以修改封装内的东西而不影响外部调用者的代码。而外部使用者只知道一个接口(函数),只要接口名不变、参数不变,使用者的代码就永远无需改变。