29类和对象---封装

1、通过双下划线设置成私有,但是依然可以访问 

class A:
    __N = 2 #类的数据属性应该就是共享的,但也可以设为私有的,此处变形为_A__N=2
    def __init__(self):
        self.__X = 10 #变形为_A__X=10
    def __foo(self):  #变形为_A__foo
        print('from A')
    def bar(self):
        self.__foo()#只有在类的内部才可以通过.__foo()调用

a = A()
a.bar()
# 在类的外部是不能通过a.__foo()访问的,要通过_类名__属性访问
print(a._A__X)
a._A__foo()
print('dict1',a.__dict__)#dict1 {'_A__X': 10}
a.__hh = 'a'
print('dict2',a.__dict__)#dict2 {'_A__X': 10, '__hh': 'a'}
#

print(a.__dict__)
a.__H = 9
# 变形只会发生在类定义的时候,之后的赋值不会变化了{'__H': 9, '_A__X': 10}
print(a.__dict__)

 2、父类如果不想让子类覆盖自己的方法,可以将方法定义为私有

class A:
    def __fa(self):  # 在定义变形为_A__fa
        print('__fa form A')

    def test2(self):
        self.__fa()  # 只会与自己所在的类为准,即调用_A__fa

class B(A):
    def __fa(self):
        print('__fa from B')

b = B()
b.test1()   #__fa form A,如果不设置为私有,那么调用__fa()时会调用class B 内的方法,打印__fa from B
b._B__fa() #__fa from B

3、封装的意义,
封装的真谛在于明确地区分内外,封装的属性可以直接在内部使用,而不能被外部直接使用,
然而定义属性的目的终归是要用,外部要想用类隐藏的属性,需要我们为其开辟接口,
让外部能够间接地用到我们隐藏起来的属性

python并不会真的阻止你访问私有属性,模块也是如此,如果要严格的控制属性的访问权限,要借助内置方法如__getattr__

(1)封装数据:
将数据隐藏起来这不是目的,将数据隐藏起来并提供对外操作的接口,
可以在这个接口上附加上对该数据的限制,以此完成对数据属性操作的严格控制。

class Teacher:
    def __init__(self, name, age):# 将name age隐藏起来
        # self.name = name
        # self.age = age
        self.set_info(name,age)
    def tell_info(self):#对外提供访问属性的接口
        print('姓名:{},年龄:{}'.format(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

t = Teacher('cc',12)
t.tell_info()

t2= Teacher('cc','12')#报错,TypeError: 年龄必须是数字

(2)封装方法:
隔离复杂度,将一些细节方法封装,并提供一个接口函数,
使用者只需知道接口函数的功能即可,至于内部的实现细节则没必要了解

#取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱
#对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,
# 很明显这么做隔离了复杂度,同时也提升了安全性
class ATM:
    def __card(self):
        print('插卡')
    def __auth(self):
        print('用户认证')
    def __input(self):
        print('输出密码')
    def __take_money(self):
        print('取钱')
    def do(self):
        self.__card()
        self.__auth()
        self.__input()
        self.__take_money()


a = ATM()
a.do()#隐藏内部的细节

 4、特性 property

装饰器,后面会自己写出来,但现在已经完全忘记了,真的想不起来了。。。。

import math
class Cirlce:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return math.pi * self.radius**2

    @property
    def perimeter(self):
        return 2 * math.pi * self.radius
c = Cirlce(3)
# 封装函数,像是一个属性
print(c.area)
print(c.perimeter)

面向对象的封装有三种方式:public、protected、private
python并没有在语法上把它们三个内建到自己的class机制中,在C++里一般会将所有的所有的数据都设置为私有的,
然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现

class Foo:
    def __init__(self,val):
        self.__NAME=val #将所有的数据属性都隐藏起来

    @property
    def name(self):
        return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置)

    @name.setter
    def name(self,value):
        if not isinstance(value,str):  #在设定值之前进行类型检查
            raise TypeError('%s must be str' %value)
        self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME

    @name.deleter
    def name(self):
        raise TypeError('Can not delete')

f=Foo('egon')
print(f.name)
f.name=10 #抛出异常'TypeError: 10 must be str'
del f.name #抛出异常'TypeError: Can not delete'

 

 

class Person:
    def __init__(self,name):
        self._name = name #_使用__name把name属性封装起来
    # 封装后外部不能访问name属性,这时对外开一个接口,可以得到name属性,比如定义一个get_name()方法
    # 但name确实是一个数据属性,用get_name()不好,这时候可以利用property装饰器
    # 将获取name方法转换为获取对象的属性,虽然调用的name()方法,但用propery装饰后,在外部看来就是调用的数据属性
    @property
    def name(self):
        return self._name
    # 前面的代码只是可以得到name属性,因为name封装的原因,不能进行赋值,比如p.name='cc'
    # 这时候再定义一个可以赋值的函数,使用@name.setter装饰,就可以对封装的name属性进行赋值了
    @name.setter
    def name(self,name):
        if type(name) is not str:#这里可以加对属性的判断条件
              raise TypeError('必须是str')
        self._name = name

p = Person('小黑')
print(p.name)   # 本质是调用name()方法,但用propery装饰后,在外部看来就是调用的数据属性

p.name = '小灰' # name是封装的属性,但用@name.setter装饰后可以像正常的属性一样进行赋值
print(p.name)

 

 

class Person:
    def __init__(self,name):
        self._name = name
    @property
    def name(self):
        a=100
        return self._name,a
    @name.setter
    def name(self,name): # name是一个列表,包含2个元素
        self._name = name[0] # 第一个元素值赋给self._name
        a=name[1] # 第二个值赋给a了,可是在执行上一个name()函数时候会有a=100,因此才不会改变变量的
p = Person('小黑')
print(p.name) # ('小黑', 100)
p.name = ['小白',99] # 因为执行了a=100,所以执行a=name[1]时,也不会改变输出值
print(p.name) #('小白', 100)

 

5、封装与扩展性,只要接口约定不变,那么则可以改变内部代码的实现,但外部调用感知不到

类的设计者
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)
>>> r1.tell_area() #使用者调用接口tell_area


#类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码
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.tell_area()

 

posted @ 2021-04-05 15:02  cheng4632  阅读(77)  评论(0编辑  收藏  举报