一. 引入
"""
面向对象三大特性: 封装, 继承, 多态, 其中最终要的特性就是封装.
什么是封装?
封装指的就是把数据与功能都整合到一起, 这里的"整合"就是封装的通俗说法.
封装的意义:
封装到对象或者类中的属性, 我们可以严格控制它们的访问, 分两步实现: 隐藏属性与开放接口
"""
二. 隐藏属性
"""
隐藏介绍:
python的class机制提供了采用双下划线开头的方式将属性隐藏起来(设置成私有的), 但这仅仅是一种变形操作, 类中所有双下划线开头的属性都会在类定义阶段, 检测语法时自动变成"_类名__属性名"的形式
如何隐藏属性?
在属性名前加__前缀, 就会实现一个对完隐藏属性效果(注意: 对外, 不是对内.且"__值__", 这种情况下不会隐藏, 这种情况下这个值会被当做内置属性来看待)
针对这种变形需要注意的问题:
1. 在类外部: 无法直接访问双下划线开头的属性, 但是知道了类名和属性名就可以拼接出名字: "_类名__属性名", 然后就可以访问了.
2. 在类内部: 可以直接访问双下划线开头的属性.(提示: 这种隐藏对外不对内)
3. 变形操作只在类定义阶段检测语法阶段发生一次, 在类定义之后的赋值操作都不会发生变形.
"""
1. 注意1举例
# 注意1: 在类外部: 无法直接访问双下划线开头的属性, 但是知道了类名和属性名就可以拼接出名字: "_类名__属性名", 然后就可以访问了.
class Foo:
__x = 1 # _Foo__x
def __f1(self): # _Foo__f1
print('from __f1')
print(Foo.__dict__) # {..., '_Foo__x': 1, '_Foo__f1': <function Foo.__f1 at 0x000001CF201FAA60>}
# print(Foo.__x) # AttributeError: type object 'Foo' has no attribute '__x'
print(Foo._Foo__x) # 1
# print(Foo.__f1) # AttributeError: type object 'Foo' has no attribute '__f1'
print(Foo._Foo__f1) # <function Foo.__f1 at 0x000001C9F0A08A60>
2. 注意2
# 注意2: 在类内部: 可以直接访问双下划线开头的属性.(提示: 这种隐藏对外不对内)
class Bar:
__x = 1 # _Bar__x
def __f1(self): # _Bar__f1
print('from __f1')
def f2(self):
print(self.__x) # print(self._Bar__x)
print(self.__f1) # print(self._Bar__f1)
# print(Bar.__x) # AttributeError: type object 'Bar' has no attribute '__x'
# print(Bar.__f1) # AttributeError: type object 'Bar' has no attribute '__f1'
obj = Bar()
obj.f2()
3. 注意3
# 注意3: 变形操作只在类定义阶段发生一次, 在类定义之后的赋值操作都不会发生变形.
class Car:
__x = 1 # _Car__x
def __f1(self): # _Car__f1
print('from __f1')
def f2(self):
print(self.__x) # print(self._Car__x)
print(self.__f1) # print(self._Car__f1)
Foo.__y = 3
print(Foo.__dict__)
print(Foo.__y) # 3
三. 开放接口: 隐藏并不是目的, 定义属性就是为了使用.
1. 隐藏数据接口
"""
将数据隐藏起来了就限制了类外部对数据的直接操作, 然后类内应该提供相应的接口来允许类外部间接的操作数据, 接口之上可以附加额外的逻辑来对数据的操作进行严格的控制.
好处: 作为接口的设计者, 可以在接口中附加任意的控制逻辑, 控制使用者对该属性的操作.(注意: 控制的是已经定义好的属性)
"""
class Student:
def __init__(self, name, age):
self.__name = name
self.__age = age
def tell_info(self):
print('控制逻辑控制使用者访问的方式'.center(50, '='))
print(f'姓名:{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
print("修改成功!")
stu_obj = Student('egon', 18)
# print(stu_obj.name) # 无法直接用名字属性
stu_obj.tell_info()
# stu_obj.set_info(1, 19) # TypeError: 控制逻辑控制使用者修改姓名的方式:名字必须执行字符串类型
# stu_obj.set_info('EGON', '99') # TypeError: 控制逻辑控制使用者修改年龄的方式:年龄必须执行整数
stu_obj.set_info('EGON', 99)
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()
四. 总结隐藏属性和开放接口
"""
2者本质: 明确的区分内外, 类内部可以修改封装内的东西而不影响外部调用者的代码
类外部只需要拿到一个接口, 只要接口名不变, 参数不变, 则无论设计者如何改变内部实现代码, 使用者无需改变代码. 这就提供了一个良好的合作基础, 只要接口这个基础约定不变, 则代码的修改不足为虑.
"""
五. property
"""
# 提示: property是用类实现的装饰器, 所以说只要是可调用对象都可以作为装饰器. 类, 函数都是可调用对象因此它们都可以被当做装饰器.(可调用对象: 可以通俗的理解为加括号就能进行调用)
# property:
将类中的函数"伪装成"对象的数据属性, 对象在访问该特殊属性时会触发功能的执行, 然后将返回值作为本次访问的结果, 然后对象通过该返回值就可以直接进行对象的操作模式.
注意: 被修饰对象需要指定返回值, 明确本次访问的结果.
# property作用:
当你某个功能在逻辑层面上是个应该直接通过"对象.属性名"直接访问的, 且被访问的对象是可能更具对象的某些数据属性动态的变化的, 这个时候我们应该使用property.
# property还提供了伪装设置和删除属性的功能:
设置: @property对象装饰完毕后拿到的返回值.setter
删除: @property对象装饰完毕后拿到的返回值.deleter
"""
1. 案例一: BMI指数应该作为数据属性的访问方式被调用
class People:
def __init__(self, name, weight, height):
self.name = name
self.weight = weight
self.height = height
# self.bmi = self.weight / (self.height ** 2) # 这里不能这样初始化, 因为对人obj对象来说, 它的bmi是动态的变化的, 我们一旦初始化以后, bmi就被固定死了.
print(property) # <class 'property'>
@property
def bmi(self): # bmi听起来更像是一个数据属性,而非功能
return self.weight / (self.height ** 2)
obj = People('MY', 65, 1.75)
print(obj.bmi)
# 当我们长高了, 这个时候我们再次访问我们的bmi值应该也要随着变化.
obj.height = 1.78
print(obj.bmi)
2. 案例二: 伪装对象查看,修改,删除数据属性的方式
# 案例二:
class People:
def __init__(self, name):
self.__name = name
def get_name(self):
return self.__name
def set_name(self, value):
if type(value) is not str:
print("请输入字符串!")
return
self.__name = value
def del_name(self):
print("对不起, 不能删除!")
# del self.__name
# 注意: 这里的name是提供使用者访问的方式. 比如使用者想要查看, 那么使用者就可以使用"对象.name"这种方式. 这个名字是要和使用者调用时所对应.
name = property(get_name, set_name, del_name)
'''
底层原理:
对象调用"obj.name"时property会触发get_name执行, 把对象本身传入, 并执行该方法体代码
对象调用"obj.name = '值'"时property会触发set_name执行, 把对象本身及"值"按照顺序窜给set_name, 执行该方法体代码
对象调用"del name"时property会触发del_name执行, 把对象本身传入, 并执行该方法体代码
'''
obj = People('egon')
# print(obj.get_name())
#
# obj.set_name("EGON")
# print(obj.get_name())
#
# obj.del_name()
# 人正常的思维逻辑
print(obj.name)
obj.set_name(1)
obj.set_name("EGON")
print(obj.name)
# 案例二另一种实现方式:
class People:
def __init__(self, name):
self.__name = name
@property # name = property(name) # 这里返回的name给下面setter或deleter操作(注意: 这里要提供返回值, 因为伪装"对象.数据属性"这种查看方式, 既然查看, 那么必须就要提供返回值)
def name(self):
return self.__name
# 注意: 下面的方法一定要基于property装饰之后, 才能使用. 因为需要拿到上面property(name)调用后的返回结果name进行setter或deleter操作
@name.setter
def name(self, value):
if type(value) is not str:
print("请输入字符串!")
return
self.__name = value
@name.deleter
def name(self):
print("对不起, 不能删除!")
# del self.__name
obj = People('egon')
# 人正常的思维逻辑
print(obj.name)
obj.name = 18
print(obj.name)
del obj.name