天天看点

python面向对象(part2)--封装

学习笔记

开发工具:Spyder

文章目录

  • ​​封装​​
  • ​​定义​​
  • ​​私有成员​​
  • ​​举个例子1​​
  • ​​举个例子2​​
  • ​​举个例子3​​
  • ​​`__slots__`属性​​
  • ​​属性`@property`​​
  • ​​案例(可读、可写)​​
  • ​​发现问题​​

封装

定义

  • 从数据角度来说

封装是将一些基本数据类型复合成一个自定义类型,即将数据与对数据的操作封装起来。

  • 从行为角度讲

封装是向类外提供功能,隐藏实现的细节;将复杂的东西藏起来,只给别人提供一种调用。

  • 从设计的角度讲

①分而治之

将一个大的需求分为许多类,每个类处理一个独立的功能。

拆分的好处:便于分工,便于复用,可扩展性强。

②封装变化

对变化的地方(行为上的变化点)独立封装(独立的放到一个类中),避免影响其他类。

③高内聚

类中各个方法都在完成一项任务(单一职责的类),即一个类可以有多个方法,每个方法都在做一个小功能,但是这些小功能都是在做同一项任务

④低耦合

类与类的关联性与依赖性要低(每个类独立),即当一个类改变时,对其他类的影响要尽量低。

(最好的低耦合是:一个类被抛弃,其他的类不受其影响)

备注1:最高的内聚,莫过于类中仅包含1个方法,但是将会导致高内聚高耦合。最低的耦合,莫过于类中包含所有方法,但是将会导致低内聚低耦合。

备注2:举个例子,电脑硬件(鼠标、键盘、内存条…)就具有高度集成化(高内聚),又可插拔(低耦合)的特点。

私有成员

  • 作用

私有成员为无需向类外提供访问的成员,可以通过私有化对成员进行屏蔽。但是在python中,并不是真正的实现了屏蔽,这只是一种障眼法(解释器会改变双下划线开头的变量名),我们可以通过【​

​_类名__成员名​

​】对私有成员进行访问。

  • 做法

在想要被私有的成员开头,加上双下划线【​

​__​

​】。

举个例子1

代码:

class Bunny:
    def __init__(self, name = "", age = 0):
        self.name = name
        self.__age = age

w01 = Bunny()
print(w01.__age)      

结果:

python面向对象(part2)--封装

报错!由报错信息可知,并不存在变量【​​

​__age​

​​】,这是因为解释器改变了【​

​__age​

​】的变量名。

举个例子2

代码:

class Bunny:
    def __init__(self, name = "", age = 0):
        self.name = name
        self.__age = age

w01 = Bunny()

print(w01._Bunny__age)
print(w01.__dict__)      

结果:

python面向对象(part2)--封装

举个例子3

我们再举一个例子,不仅实现成员的私有,而且在创建实例对象时,对参数进行判断。

代码:

class Bunny:
    def __init__(self, name, age):
        self.set_name(name)
        self.set_age(age)
    
    def set_name(self, value):
        self.__name = value
    
    def get_name(self):
        return self.__name

    
    def set_age(self, value):
        if 0<= value < 7:
            self.__age = value
        else:
            self.__age = 0
            print("输入错误")
    def get_age(self):
        return self.__age


b01 = Bunny("大白", 5)
b01.set_name("小黄")
b01.set_age(10)
b01.set_age(4)
print(b01.get_name())
print(b01.get_age())      

结果:

python面向对象(part2)--封装

​__slots__​

​属性

  • 作用

限定一个类创建的实例,只能有固定的实例变量,不能再额外添加。

  • 语法
__slots__ = ("变量名1","变量名2"…..)      
  • 说明

含有​

​__slots__​

​​属性的类所创建的对象没有​

​__dict__​

​属性, 即此实例不用字典来存储对象的实例属性。

  • 优缺点

优点:访止用户因错写属性的名称而发生程序错误。

缺点:丧失了动态语言可以在运行时为对象添加变量的灵活性。

  • 例子(不使用​

    ​__slots__​

    ​)

代码:

class Bunny:
    def __init__(self, name, age):
        self.name = name
        self.age = age


w01 = Bunny("小黄", 6)
w01.sex = "公"
print(w01.__dict__)      

结果:

python面向对象(part2)--封装
  • 例子(使用​

    ​__slots__​

    ​将实例变量数固定)

代码:

class Bunny:
    __slots__ = ("name", "age")
    def __init__(self, name, age):
        self.name = name
        self.age = age


w01 = Bunny("小黄", 6)
w01.sex = "公"      

结果:

python面向对象(part2)--封装

由结果可知可知,当我们自己多增加一个实例变量时,报错了,python提示我们,Bunny类中没有属性sex.

属性​

​@property​

  • 定义及调用
python面向对象(part2)--封装
  • 说明

①通过两个公开的属性,保护一个私有的变量。

②​​

​@property​

​​负责读取,​

​@属性名.setter​

​负责写入

案例(可读、可写)

在做这个案例之前我们先回顾一个知识点:

python面向对象(part2)--封装

代码第9行:若直接将list01赋值给list02,则list02得到的是list01所指的对象的地址。

代码第10行:若将list01[:]赋值给list03,则list03得到的是list01新创建的对象的地址。

若每次通过切片返回新对象(即第10行的操作),都会另外开辟空间,创建新对象,占用过多内存。

我们看以下代码:

class Bunny:
    def __init__(self, name):
        
        self.name = name
        self.__foods = []
        self.__weight = 7
        
    @property
    def name(self):
        return self.__name
    
    @name.setter
    def name(self, value):
        self.__name = value
        
    @property
    def weight(self):
        return self.__weight
    


b01 = Bunny("小白")

print(b01.name, b01.weight)      

结果:

python面向对象(part2)--封装

在代码中,我们设置变量name为可读可写,而变量weight设置为只读(即只可读取不可以更改),如果我们试图更改weight则会报错。

发现问题

备注:我发现在可读可写的情况下(如变量name),在​

​__init__()​

​​方法中,变量(name)前可以不加双下划线,即可以写成【​

​self.name = name​

​​】,而只读情况下(如变量weight),在​

​__init__()​

​​方法中,变量(weight)前要加双下划线,即可以写成【​

​self.__weight = 7​

​】,若不加双下划线则会报错【AttributeError: can’t set attribute】。这是为啥呢?

比如:

代码1:

class Bunny:
    #__slots__ = ("__name", "__weight")
    def __init__(self, name):
        
        self.name = name
        self.weight = 7
        
    @property
    def name(self):
        return self.__name
    
    @name.setter
    def name(self, value):
        self.__name = value
        
        
    @property
    def weight(self):
        return self.__weight
    
b01 = Bunny("小白")
print(b01.name, b01.weight)      

结果1:

python面向对象(part2)--封装

代码2:

class Bunny:
    #__slots__ = ("__name", "__weight")
    def __init__(self, name):
        
        self.name = name
        self.__weight = 7
        
    @property
    def name(self):
        return self.__name
    
    @name.setter
    def name(self, value):
        self.__name = value
        
        
    @property
    def weight(self):
        return self.__weight
    
b01 = Bunny("小白")
print(b01.name, b01.weight)      

结果2:

python面向对象(part2)--封装

代码3:

class Bunny:
    #__slots__ = ("__name", "__weight")
    def __init__(self, name):
        
        self.name = name
        self.weight = 7
        
    @property
    def name(self):
        return self.__name
    
    @name.setter
    def name(self, value):
        self.__name = value
        
        
    @property
    def weight(self):
        return self.__weight
    @weight.setter
    def weight(self, value):
        self.__weight = value
    

b01 = Bunny("小白")
print(b01.name, b01.weight)      

结果3:

python面向对象(part2)--封装

我们改一下weight试试:

代码:

class Bunny:
    def __init__(self, name):
        
        self.name = name
        self.__foods = []
        self.__weight = 7
        
    @property
    def name(self):
        return self.__name
    
    @name.setter
    def name(self, value):
        self.__name = value
        
    @property
    def weight(self):
        return self.__weight
    


b01 = Bunny("小白")
b01.weight = 10      

结果:

python面向对象(part2)--封装

报错了!

接下来,我们把foods设置为只读,并试着更改一下foods:

class Bunny:
    def __init__(self, name):
        
        self.name = name
        self.__foods = ["提草", "兔粮"]
        self.__weight = 7
        
    @property
    def name(self):
        return self.__name
    
    @name.setter
    def name(self, value):
        self.__name = value
        
    @property
    def weight(self):
        return self.__weight
    
    @property
    def foods(self):
        return self.__foods
    


b01 = Bunny("小白")
b01.foods = ["提草", "兔粮", "白菜"]      

结果:

python面向对象(part2)--封装

嗯!报错了。

但在只读情况下真的不可以更改么?我们看一下以下这段代码:

class Bunny:
    def __init__(self, name):
        
        self.name = name
        self.__foods = ["提草", "兔粮"]
        self.__weight = 7
        
    @property
    def name(self):
        return self.__name
    
    @name.setter
    def name(self, value):
        self.__name = value
        
    @property
    def weight(self):
        return self.__weight
    
    @property
    def foods(self):
        return self.__foods
    


b01 = Bunny("小白")
b01.foods.append("白菜")
print(b01.name, b01.weight, b01.foods)      

结果:

python面向对象(part2)--封装

惊!不仅没有报错,而且真的修改了b01.foods所关联的列表。

我们画一个简易内存图来解释一下:

①当执行【​

​b01 = Bunny("小白")​

​​】时:

python面向对象(part2)--封装

②当执行【​

​b01.foods.append("白菜")​

​​】时,【​

​b01.foods​

​​】返回了【​

​self.__foods​

​​】,【​

​self.__foods​

​​】提供了可变对象的地址(foods关联的是列表对象,列表是可变类型),通过地址,我们找到了列表对象,并对列表对象进行修改。但是在Bunny实例对象内部,存放的foods所关联对象的地址没有变,改变的只是列表对象本身,这种情况满足只读属性的性质:

python面向对象(part2)--封装

但是如果直接用【​

​b01.foods = ["提草", "兔粮", "白菜"]​

​】的方式对变量foods进行更改,则在内存中会创建新的列表对象,Bunny实例对象内部所存储的foods所关联对象的地址则会改变,则不满足只读属性的性质。

但如果我们再更改一下代码,返回foods列表的切片:

class Bunny:
    def __init__(self, name):
        
        self.name = name
        self.__foods = ["提草", "兔粮"]
        self.__weight = 7
        
    @property
    def name(self):
        return self.__name
    
    @name.setter
    def name(self, value):
        self.__name = value
        
    @property
    def weight(self):
        return self.__weight
    
    @property
    def foods(self):
        return self.__foods[:]
    


b01 = Bunny("小白")
b01.foods.append("白菜")
print(b01.name, b01.weight, b01.foods)