08-02 封装

一. 引入

"""
面向对象三大特性: 封装, 继承, 多态, 其中最终要的特性就是封装.
什么是封装?
    封装指的就是把数据与功能都整合到一起, 这里的"整合"就是封装的通俗说法.
封装的意义:
    封装到对象或者类中的属性, 我们可以严格控制它们的访问, 分两步实现: 隐藏属性与开放接口
"""

二. 隐藏属性

"""
隐藏介绍:
    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
posted @ 2020-04-08 14:21  给你加马桶唱疏通  阅读(181)  评论(0编辑  收藏  举报