1.引子
面向对象编程有三大特性:封装、继承、多态,其中最重要的一个特性就是封装。封装指的就是把数据与功能都整合到一起,听起来是不是很熟悉,没错,我们之前所说的”整合“二字其实就是封装的通俗说法。除此之外,针对封装到对象或者类中的属性,我们还可以严格控制对它们的访问,分两步实现:隐藏与开放接口
2.隐藏属性
类中存放的有数据属性和函数属性 , 所谓的隐藏属性 , 就是隐藏这两种,隐藏字面意思 , 就是别人看不到,那么在类中我们如何隐藏呢?
2.1隐藏数据属性
Python的Class机制采用双下划线开头的方式将属性隐藏起来(设置成私有的),但其实这仅仅只是一种变形操作,类中所有双下滑线开头的属性都会在类定义阶段、检测语法时自动变成“_类名__属性名”的形式:
class Foo:
__N=0 # 变形为_Foo__N
def __init__(self): # 定义函数时,会检测函数语法,所以__开头的属性也会变形
self.__x=10 # 变形为self._Foo__x
print(Foo.__N) # 报错AttributeError:类Foo没有属性__N
obj = Foo()
print(obbj.__x) # 报错AttributeError:对象obj没有属性__x
当然有人可能疑问 , 我Foo._Foo__N不就可以访问了吗?确实可以访问,注意我们隐藏的目的就是不能让外面直接访问,如果你想访问,那就不要隐藏。隐藏属性本质上是一种变形方法,目的就是为了不能在方面直接访问,但是可以在类内直接访问
class Foo:
__N=0 # 变形为_Foo__N
def __init__(self): # 定义函数时,会检测函数语法,所以__开头的属性也会变形
self.__x=10 # 变形为self._Foo__x
print(Foo.__N)
obj = Foo() # 0
注意: 这种变形操作只在类体代码定义的时候变形 , 在类定义之后的赋值操作,不会变形。
>>> Foo.__M=100
>>> Foo.__dict__
mappingproxy({..., '__M': 100,...})
>>> Foo.__M
100
>>> obj.__y=20
>>> obj.__dict__
{'__y': 20, '_Foo__x': 10}
>>> obj.__y
20
2.2隐藏函数属性
目的的是为了隔离复杂度,例如ATM程序的取款功能,该功能有很多其他功能组成,比如插卡、身份认证、输入金额、打印小票、取钱等,而对使用者来说 , 只需要开发取款这个功能接口即可,其余功能我们都可以隐藏起来
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()
obj = ATM()
obj.withdraw()
3.开放接口
定义属性就是为了使用,所以隐藏并不是目的
接口意义:
作为类的设计者可以在接口上附加任意逻辑,来严格控制使用者对属性的操作(删除,修改,查看)
比如我想修改学生对象的属性中的学校名字,必须是字符串
class Student:
__school = "北大"
def __init__(self, name, age):
self.name = name
self.age = age
def set_school_name(self, school_name):
if type(school_name) is str:
Student.__school = school_name
else:
print("输入错误: sbsbsbsbsb")
obj = Student('小张',20)
obj.set_school_name(986567)
4.小总结
隐藏属性并不是不给外部用 , 而是想让外部间接的使用 , 用接口--->内部功能的高度封装
总结隐藏属性与开放接口,本质就是为了明确地区分内外,类内部可以修改封装内的东西而不影响外部调用者的代码;而类外部只需拿到一个接口,只要接口名、参数不变,则无论设计者如何改变内部实现代码,使用者均无需改变代码。这就提供一个良好的合作基础,只要接口这个基础约定不变,则代码的修改不足为虑。
5.property
property是用类实现的一种装饰器 , 简单回顾一下装饰器
装饰器是在不修改被装饰对象源代码以及调用方式的前提下为被装饰对象添加新功能的可调用对象
property是一个类实现的装饰器,是用来干什么???
一般动态生成的数据属性我们可以用property伪装其是功能的调用
案例1:
# 案例1
class People:
def __init__(self, height, weight):
self.height = height
self.weight = weight
"""
从bmi的公式上看,bmi应该是触发功能计算得到的
bmi是随着身高、体重的变化而动态变化的,不是一个固定的值
说白了,每次都是需要临时计算得到的
但是bmi听起来更像是一个数据属性,而非功能,最后调用的时候是直接调用属性的那种
property刚好可以伪装绑定方法为一个属性
"""
@property
def bmi(self):
return self.weight / (self.height ** 2)
obj = People(1.83, 70)
print(obj.bmi) # 20.1778676
property和隐藏属性相结合 , 同时使用property有效地保证了属性访问的一致性。另外property还提供设置和删除属性的功能,如下
"""需求:比如我有一个name属性想要隐藏但是我还要想访问他(改 删 查),调接口但是外部我想更符合逻辑,毕竟是属性,我想直接点调用"""
class People: def __init__(self): self.__name = "张三" def get_name(self): return self.__name def set_name(self, val): if not type(val) is str: print("输入字符串") return self.__name = val def del_name(self): print('就是不给删除') # 这种做法实际上是老版本的操作 name = property(get_name, set_name, del_name)obj = People()# 获取属性# print(obj.get_name())print(obj.name)# 修改属性obj.name = '李四'# 删除属性del obj.name
案例2的改良版本
class People: def __init__(self): self.__name = "张三" @property def name(self): # obj.name return self.__name @name.setter def name(self, val): # obj.name="李四" if not type(val) is str: print("输入字符串") return self.__name = val @name.deleter def name(self): # del obj.name print('就是不给删除')obj = People()# 获取属性# print(obj.get_name())print(obj.name)# 修改属性obj.name = '李四'print(obj.name) # 李四# 删除属性del obj.name# 注意:函数名必须是你要调用的属性的名字
5.总结
面向对象虽然有三大特征 , 封装 , 继承 , 多态 , 但是我觉得封装才是面向对象的核心 , 白话讲就是整合 , 把数据和功能整合到一个小容器里面 , 这个容器就是对象 , 你在一个文件中拿到这个对象 , 就拿到了他所有的数据和功能 , 对于代码的设计和编写就比较费头脑设计了 , 不过好的一点就是 , 代码的扩展性很强 , 你可以开始想到的只是一个基础版本的封装的数据和功能 , 但是你后期想到了再加进来 , 仍然不影响整个代码的逻辑和调用 , 还有一点就是面向对象只是提高了代码的扩展性 , 你不用面向对象写代码并不代表着你的代码垃圾 , 你用面向对象写代码 , 也不一定非要用到所有的特征 , 至于写出来的代码符不符合面向对象的代码 , 主要还是看你的代码规范 , 就比如定义私有属性最好在__init__
里面 , 如果初始化没有值 , 可以先定义为None , 然后常见的搭配使用就ok了 , 这也是他们为什么说你写多了就会了, 确实是这样的 , 面向对象只是一个编程范式 , 写代码比较头疼的就是设计类 , 根据需求来设计 , 再加上自己实际生活的一些归类去处理