1.什么是封装:?
封:属性对外是隐藏的,但对内是开放的。
装:申请一个名称空间,往里装入一系列名字/属性。(类和对象都是有一个名称空间,往里面装一系列的名字)
2、为什么要封装
    封装数据属性的目的
        首先定义属性的目的就是为了给类外部的使用使用的,
        隐藏之后是为了不让外部使用直接使用,需要类内部开辟一个接口
        然后让类外部的使用通过接口来间接地操作隐藏的属性。
        精髓在于:我们可以在接口之上附加任意逻辑,从而严格控制使用者对属性的操作

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



3、如何封装
注意:
如何隐藏?
再属性前面加__开头

重点:这种隐藏式对外不对内的,即再类的内部可以直接访问,而在类的外部则无法直接访问。原因是在类定义阶段,类体内代码
统一发生了一次变形。
下面是证明上面一行话的例子:
一种情况:
再类的里面隐藏数据属性,类再内部可以访问到这个隐藏的属性,类再类的外部就访问不到这个隐藏的属性了
class People:
    __county='china' #再想隐藏的属性前面加__开头
    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex
    def eat(self):
        print('eat...')
        print(People.__county)  #再类的内部调用改变过的属性。通过类名调用属性。
peo1=People('egon',18,'male')
# People.eat(111)   #调用函数后,print(People.__county)能访问到,说明对内能访问到
print(People.__county) #再外部直接访问改变后的属性,会报错
二种情况:
# 再类的里面隐藏数据属性或者函数属性,对象再内部可以访问到隐藏的属性,对象再类的外部就访问不到隐藏的属性。
# class People:
#     __county='china' #再想隐藏的属性前面加__开头
#     def __init__(self,name,age,sex):
#         self.__name=name   #隐藏属性名
#         self.age=age
#         self.sex=sex
#     def eat(self):
#         print('eat...')
#         print(self.__name)    #对象在类内部调用隐藏的属性
# peo1=People('egon',18,'male')
# peo1.eat()  #验证对象在类内部可以访问到隐藏的属性。
# print(peo1.__name) #验证对象在类外部访问不到隐藏的属性。


封装的底层原理

2.这种语法上的变形只在定义阶段发生一次,因为类体代码仅仅只在类定义阶段检查一次
例如:
# 只要是__开头的属性,它在往名称空间里面丢的时候,都做了一个变形。成了_类名__属性名
class People:
    __county='china' #_People__county
    __n=100
    def __init__(self,name,age,sex):
        self.__name=name   #
        self.age=age
        self.sex=sex
    def eat(self):
        print('eat...')
        # print(People.__county)
        print(self.__county)
s1=People('egon',18,'male')
s1.eat()
# print(People.__dict__)  #查看People里面的隐藏属性都做了变形,成了_People__county
print(People._People__county)  #直接访问这个变了形的属性名,可以直接访问到
People.__x=1    #再这里__x不会变形,因为这个变形只在类定义阶段发生一次,定义阶段变形后,其他再操作__开头都没用了
print(People.__dict__)
# 总结:这种隐藏仅仅是一种语法上的变形操作。

为什么在内部可以访问到被隐藏的属性?
因为类再定义阶段就会执行类体代码,执行之前会先检测语法,这个检测只在类定义阶段发生一次
再内部能访问到,是因为内部的访问再当初检测语法的时候,都一块变形了。都变成了
最真实的形式
实例训练:
class Foo:
    def __f1(self):   #_Foo__f1
        print('Foo.f1')
    def f2(self):
        print('Foo.f2')
        self.__f1()     #self._Foo__f1()
class Bar(Foo):
    def __f1(self):     #_Bar__f1
        print('Bar.f1')
obj=Bar()
obj.f2()

结果是:

Foo.f2
Foo.f1

总结:如果不想让子类的方法覆盖父类的,可以该方法名前加一个__开头。


用隐藏属性这个方法,是如何用呢?
封装数据属性的目的
    首先定义属性的目的就是为了给类外部的使用使用的,
    隐藏之后是为了不让外部使用直接使用,需要类内部开辟一个接口
    然后让类外部的使用通过接口来间接地操作隐藏的属性。
    精髓在于:我们可以在接口之上附加任意逻辑,从而严格控制使用者对属性的操作
# 想严格控制属性的类型。要做两步:
#     一步是:隐藏属性,外部看不到,想看设计者可以在内部开一个接口,
#         直接用这个接口,再接口内附加一些控制的逻辑
    def set_info(self,name,age):    #开一个接口,可以改里面的属性。
        if type(name) is not str:
            # print('用户名必须为str类型')
            # return
            raise TypeError('用户名必须为str类型')

        if type(age) is not int:
            # print('年龄必须为int类型')
            # return
            raise TypeError('年龄必须为int类型')
        self.__name=name
        self.__age=age

peo1=People('egon',18)
# peo1.name=123  #再原来没有隐藏属性的时候,类型可以随便用,没限制,123也没关系
# peo1.age
# peo1.tell_info()

peo1.set_info('egon',19)
# peo1.tell_info()