面对对象之封装

什么是封装?

通俗的说,封装就是将你不想让别人看到的东西隐藏起来,在程序中就是说你可以把一些不需要别人看到的属性和实现的方法隐藏起来,只留下一些可以访问和调用的方法给别人。

封装的好处?

封装的好处就是便于使用,因为复杂内容可以隐藏起来不用看外界看,而且具有安全性,可以保证你的一些数据不会被外界修改。

封装语法的使用:

# 在python中用双下划线开头的方式将属性隐藏起来,也就是设置为私有的。

class Foo:
    __x = 111
    def __init__(self,name,age):
        self.__name = name
        self.__age = age

    def __func(self):
        print('func')

    def get_info(self):
        print(self.__name,self.__age)


obj = Foo('egon',12)
print(obj.x)  # Foo' object has no attribute 'x'  # foo'对象没有属性'x'
print(obj.name)  # 'Foo' object has no attribute 'name'  # “foo”对象没有属性“name”
print(obj.func)  # 'Foo' object has no attribute 'func'  # “foo”对象没有属性“func”
obj.get_func()  # egon 12  #提出疑问,为什么在这里可以访问到结果

#在类中定义的属性以及方法名前面加上__之后,我们在外部就不能正常的进行调用了,说明它们已经被“隐藏”了起来

我们要明白一点,封装的主要目的并不是让你完全无法读取到里面的内容,那么我们该如何来进行读取,让我们先来看一下封装的本质

#封装的本质很简单,它其实是在类的定义阶段将你加了__的名称变了个形而已
#类中所有双下划线开头的名称比如__x都会在类定义时自动变形成:_类__x的形式

#根据上述的原则,我们来标注一下上面代码在定义阶段发生的事情
class Foo:
    __x = 111  # 在定义阶段实际上变形成了_Foo__x
    def __init__(self,name,age):
        self.__name = name   #  在定义阶段实际上变形成了self._Foo__name
        self.__age = age  # 在定义阶段实际上变形成了self._Foo__age

    def __func(self):  # 在定义阶段实际上变形成了_Foo__func(self)
        print('func')

    def get_info(self):
        print(self.__name,self.__age)  # 和上面的过程一样,内容实际上也变成了self._Foo__name,sele._Foo__age
# 通过这样的分析我们可以想到用这样的方式来从外部调用
obj = Foo('egon',12)
print(obj._Foo__x)  #  111 
print(obj._Foo__name)  #  egon
print(obj._Foo__func())  #  func
obj.get_info()  # egon 12  # 根据前面3个情况我们可以知道,实际上调用这个函数时,它在内部已经变成了_类__名称的形式,所以当然能够找到

#举一个__另一个用处,当父类的方法不想让子类覆盖时
class Foo:
    def f1(self):
        print('Foo.f1')

    def f2(self):
        print('Foo.f2')
        self.f1()

class Bar(Foo):
    def f1(self):
        print('Bar.f1')

obj = Bar()
obj.f2()
>>>:Foo.f2
         Bar.f1 
#在这个例子中子类的f1方法就覆盖了父类中的f1方法,所以结果是Foo.f2和Bar.f1
#可如果我们不想让它覆盖父类中的f1方法呢?那就这样
class Foo:
    def __f1(self):  # _Foo__f1
        print('Foo.f1')

    def f2(self):
        print('Foo.f2')
        self.__f1()  #  _Foo__f1

class Bar(Foo):
    def __f1(self):  #  _Bar__f1
        print('Bar.f1')

obj = Bar()
obj.f2()
>>>:Foo.f2
         Foo.f1
#当加上双下划线之后,在类的定义阶段,父类f1就被变形成了_Foo__f1,而子类中的f1则变成了_Bar__f1,所以虽然看起来都是__f1,但实际上他们是不相同的,当然不会被覆盖

总结一下:

1、__开头的属性实现的隐藏仅仅是一种语法意义上的变形,并不会真的限制类外部的访问。

2、该变形操作只是在类定义阶段检测语法时发生一次,类定义阶段之后新增的__开头的属性并不会变形。

3、如果父类不想让子类覆盖自己的属性,可以在属性前加__开头。

虽然我们了解了封装的原理并且知道了可以通过_类名__名称这样的方式依旧可以看到属性,但实际上我们会这么做吗?并不会。如果我们要通过这种方式来取得我们封装起来的属性那么我们封装它的意义在哪?还不如不进行封装。那封装的真正用途在哪?让我们了解一下:

封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。

#类的设计者
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name = name
        self.owner = owner
        self.__width = width
        self.__length = length
        self.__high = high
    def tell_area(self):  # 对外提供的方法,隐藏了内部实现的细节,此时我们需要求的是面积
        return self.__width * self.__length

#使用者
r1 = Room('卧室','egon',20,20,20)
res = r1.tell_area()  # 使用者调用接口tell_area
print(res)   #  400

#类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name = name
        self.owner = owner
        self.__width = width
        self.__length = length
        self.__high = high
    def tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了
        return self.__width * self.__length * self.__high
#对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能
r1 = Room('卧室','egon',20,20,20)
res = r1.tell_area()
print(res)

property装饰器

通过方法来修改或访问属性,本身没有什么问题,但对于使用者来说还是有一些麻烦的地方,使用必须知道哪些是普通属性,哪些是私有属性,需要使用不同的方式来调用他们。

而接下来要了解的property装饰就是为了使得调用方式一致。

有三个相关的装饰器

1.property 该装器用在获取属性的方法上
2.@key.setter 该装器用在修改属性的方法上
3.@key.deleter 该装器用在删除属性的方法上

注意:key是被property装饰的方法的名称 也就是属性的名称 (加@property可以让类中的方法变成属性,从而达到不被修改的目的)
内部会创建一个对象 变量名称就是函数名称
所以在使用setter和deleter时 必须保证使用对象的名称取调用方法
所以是 key.setter

class A:
    def __init__(self,name,key):
        self.__name = name
        self.__key = key

    @property
    def key(self):
        return self.__key

    @key.setter
    def key(self,new_key):
        if new_key <=100:
            self.__key = new_key
        else:
            print('key 必须小于等于100')

    @key.deleter
    def key(self):
        print('不允许删除该属性')
        del self.__key

a = A('jack',321)
print(a.key)
a.key =123
print(a.key)

 练习

class Square:  # 正方形面积

    def __init__(self, width):
        self.width = width

    @property  # 只要 . 这个属性, 就会自动触发这个函数
    def area(self):
        return self.width * self.width


s = Square(10)
print(s.area)
#BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)
'''
成人的BMI数值:
过轻:低于18.5
正常:18.5-23.9
过重:24-27
肥胖:28-32
非常肥胖, 高于32
  体质指数(BMI)=体重(kg)÷身高^2(m)
  EX:70kg÷(1.75×1.75)=22.86
'''
class People:
    def __init__(self,name,weight,height):
        self.name = name
        self.weight = weight
        self.height = height
    @property
    def bmi(self):
        return self.weight/(self.height**2)
s = People('chouqing',75,1.75)
print(s.bmi)