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

学习笔记
开发工具:Spyder



##封装

定义

  • 从数据角度来说

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

  • 从行为角度讲

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

  • 从设计的角度讲

①分而治之
将一个大的需求分为许多类,每个类处理一个独立的功能。
拆分的好处:便于分工,便于复用,可扩展性强。

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

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

④低耦合
类与类的关联性与依赖性要低(每个类独立),即当一个类改变时,对其他类的影响要尽量低。
(最好的低耦合是:一个类被抛弃,其他的类不受其影响)

备注1:最高的内聚,莫过于类中仅包含1个方法,但是将会导致高内聚高耦合。最低的耦合,莫过于类中包含所有方法,但是将会导致低内聚低耦合。
备注2:举个例子,电脑硬件(鼠标、键盘、内存条…)就具有高度集成化(高内聚),又可插拔(低耦合)的特点。

私有成员

  • 作用

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

  • 做法

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

举个例子1

代码:

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

w01 = Bunny()
print(w01.__age)

结果:

报错!由报错信息可知,并不存在变量【__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__)

结果:

举个例子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())

结果:

__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__)

结果:

  • 例子(使用__slots__将实例变量数固定)

代码:

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


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

结果:

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

属性@property

  • 定义及调用

  • 说明

①通过两个公开的属性,保护一个私有的变量。
@property负责读取,@属性名.setter负责写入

案例(可读、可写)

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

代码第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)

结果:

在代码中,我们设置变量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:

代码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:

代码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:


我们改一下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

结果:

报错了!

接下来,我们把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 = ["提草", "兔粮", "白菜"]

结果:

嗯!报错了。

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

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)

结果:

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

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

①当执行【b01 = Bunny("小白")】时:

②当执行【b01.foods.append("白菜")】时,【b01.foods】返回了【self.__foods】,【self.__foods】提供了可变对象的地址(foods关联的是列表对象,列表是可变类型),通过地址,我们找到了列表对象,并对列表对象进行修改。但是在Bunny实例对象内部,存放的foods所关联对象的地址没有变,改变的只是列表对象本身,这种情况满足只读属性的性质:

但是如果直接用【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)

结果:

由结果可知,b01.foods并没有被更改。这是为啥呢?
这是因为我们在执行【b01.foods.append("白菜")】时,【b01.foods】返回的是【self.__foods[:]】,也就是说,返回了【self.__foods】的切片,即返回了一个新创建的列表对象的地址。则,我们通过这个新地址找到的新列表对象,并不是【self.__foods】所关联的列表对象,所以就无法对b01.foods所关联的原列表对象进行修改。

发布了116 篇原创文章 · 获赞 22 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/m0_37422217/article/details/105119752
今日推荐