跳转至

封装与接口

封装

☆什么是封装?

[封:] 属性对外是隐藏的, 对内是开放的..
装: 申请一个名称空间,往里面装入一系列的名字/属性..

不用做任何操作, 类与实例化对象就满足装的概念啦;但不满足封, 因为它们的属性, 内外都能访问的到.

class People:
    country = 'China'

    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex
        print(People.country)  # -- 内部访问 类属性

    def eat(self):
        print('%s is eating....' % self.name)  # -- 内部访问 实例化对象属性

    # print(People.__country)  # 注意哦!报错,因为People类在这里还没有定义完..

peo1 = People('小川', 20, 'male')
print(peo1.name)  # -- 外部访问 实例化属性
print(People.country)  # -- 外部访问 类属性

☆如何封装?

在属性前加上__开头..
这种隐藏是对外不对内的, 即在类的内部可以直接访问,而在类的外部无法直接访问..

class People:
    __country = 'China'

    def __init__(self, name, age, sex):
        self.__name = name
        self.age = age
        self.sex = sex

    def speak(self):
        print('is speaking...')
        print(People.__country)

    def eat(self):
        print('is eating...')
        print(self.__name)

    def __run(self):
        print('is running...')


# -- 类属性 内部能访问,外部不能
People.speak(123)
# People.__run(123)  # type object 'People' has no attribute '__run'
# print(People.__country)  # type object 'People' has no attribute '__country'

# -- 实例化对象属性 内部能访问,外部不能
peo1 = People('小川', 20, 'male')
peo1.eat()
# print(peo1.__name)  # 'People' object has no attribute 'name'

☆封装底层原理

封装的隐藏属性的底层原理/特定:
1> 这种隐藏仅仅只是一种 语法上的变形操作 _类名__属性名
2> 这种语法上的变形 只在类定义阶段发生一次 .. 因为类体代码仅仅只在类定义阶段检测一次..
3> 这种隐藏是 对外不对内 的,即在类的内部可以直接访问,而在类的外部无法直接访问, 原因是
     在类定义阶段, 类体内代码 统一 发生了一次变形...
4> 如果不想让子类的方法覆盖父类的, 可以将该方法名前加一个__开头.

当运行到类体代码,会从上到下,先检测当前行的语法(遇到函数的话,也会先检测函数体里的代码), 在检测过程中就将封装属性的名字进行了变形!检测后执行当前行代码.丢进名称空间的名字就变成了检测时变形的名字. 所以能在内部可以直接访问到,在外部不能直接访问到..
阐述的好啰嗦... 反复横跳 只可意会不可言传.(´▽`)

# {'_People__name': '小川', 'age': 20, 'sex': 'male'}
print(peo1.__dict__)
# {... '_People__country': 'China', ... ,
# '_People__run': <function People.__run at 0x7fedb7f1cc10> ...}
print(People.__dict__)

这意味着,在类体外,以封装的方式试图添加一个隐藏属性..不会对其变形!!

# {'_People__name': '小川', 'age': 20, 'sex': 'male', '__height': 173}
peo1.__height = 173
print(peo1.__dict__)

在继承那一小节,分析 '实例化对象查找属性的顺序'时, 举了以下这个例子:
验证上方底层原理的第4点 ...不想让子类的方法覆盖父类的... : 若想访问到的是A类里的func1,如何做??
采用双下划线开头的方式将方法设置为私有的!

class A:
    def func1(self):
        print('A.func1')

    def func2(self):
        print('A.func2')
        self.func1() # obj.func1()

class B(A):
    def func1(self):
        print('B.func1')

obj = B()
obj.func2()
"""
A.func2
B.func1
"""

# -- ( ̄O ̄;) 用封装将其变个形!!看似相同实则发生了变形.
# 子类隐藏父类不隐藏;父类隐藏子类不隐藏;都隐藏 ... 都能实现上方变更的需求.

class A:
    def __func1(self): # _A.__func1
        print('A.func1')

    def func2(self):
        print('A.func2')
        self.__func1() # self._A__func1  obj._A__func1

class B(A): 
    def __func1(self): # _B__func1
        print('B.func1')

obj = B()
obj.func2()
"""
A.func2
A.func1
"""

# -------------
class Foo:
    __x = 111  # _Foo__x

class Bar(Foo):
    __x = 222  # _Bar__x -- 没有覆盖的效果

☆开发接口

隐藏数据属性

封装数据属性的目的/应用场景:
       首先定义属性的目的就是为了给类外部的使用者使用的.
       隐藏之后是 为了不让外部使用者直接使用 ,需要在类内部开辟一个接口.
       然后让类外部的使用者通过接口来间接地操作隐藏的属性.

       精髓在于 -- 我们可以在接口之上附加任意的逻辑,从而严格控制使用者对属性的操作 !'增删改查'

class People:

    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    def tell_info(self):
        print(f'姓名:{self.__name},年龄:{self.__age}')

    def set_info(self, name, age):
        if type(name) is not str:
            print('用户名必须为str类型!')
            return
        if not isinstance(age, int):
            # 主动抛出一个错误让程序结束运行
            raise TypeError('年龄必须为整型!')

        self.__name = name
        self.__age = age


peo1 = People('egon', 18)
peo1.set_info('小川', 20)
peo1.tell_info()  # 姓名:小川,年龄:20
"""
封装数据属性并不是让使用者不用,只是不让他直接用,让其使用内部开发的接口实现间接使用,
而开发者可以在接口上添加逻辑严格控制使用者的操作行为
"""
隐藏函数属性

封装函数属性的目的:
       首先定义属性的目的就是为了给类外部的使用者使用的.
       隐藏函数属性是为了不让外部使用者直接使用,需要类内部开辟一个接口.
       然后在接口内去调用隐藏的功能

       精髓在于 --  隔离了复杂度!

栗子0: 电视机本身是个黑盒子,隐藏了所有细节,但是一定会对外提供一堆按钮,这些按钮也正是接口的概念 
栗子1: 快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来啦.

"""
取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱
对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来
很明显这么做,隔离了复杂度,同时也提升了安全性
"""
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装饰器用于将被装饰的方法 伪装成一个数据属性 ,在使用时可以不用加括号而直接使用.

class People:
    def __init__(self, name, weight, height):
        self.name = name
        self.weight = weight
        self.height = height
        # 不妥, 因为bmi指数应该是随着身高体重的变化而变化的
        # self.bmi = self.weight / (self.height**2)

    @property
    def bmi(self):
        return self.weight / (self.height**2)

peo1 = People('egon', 75, 1.8)
print(peo1.bmi)  # 23.148148148148145

# BMI指数听起来更像是一个特征(数据属性)而不是技能(函数属性).
# @property 将bmi这个技能伪装成了一个特征

结合封装的应用

与封装的属性 结合着 使用! >> 查看 - 修改 - 删除 <<

class People:

    def __init__(self, name):
        self.__name = name

    @property
    def name(self):
        return f'姓名:{self.__name}'

    @name.setter  # 前提是name方法被property装饰过了.
    def name(self, name):
        if type(name) is not str:
            raise TypeError('名字必须为str类型..')
        self.__name = name

    @name.deleter
    def name(self):
        # del self.__name
        # raise PermissionError('不允许删除!')
        print('不允许删除!')


peo1 = People('egon')
print(peo1.name)  # 姓名:egon
peo1.name = '小川'
print(peo1.name)  # 姓名:小川

del peo1.name  # 不允许删除!

"""Ps 远古的用法,实现的效果是一样的.
class People:

    def __init__(self, name):
        self.__name = name

    def get_name(self):
        return f'姓名:{self.__name}'

    def set_name(self, name):
        if type(name) is not str:
            raise TypeError('名字必须为str类型..')
        self.__name = name

    def del_name(self):
        print('不允许删除!')

    name = property(get_name, set_name, del_name)

peo1 = People('egon')
print(peo1.name)  # 姓名:egon
peo1.name = '小川'
"""