学习笔记
开发工具: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)

结果:
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)--封装_低耦合_02

举个例子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)--封装_封装_03

​__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)--封装_下划线_04

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

代码:

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


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

结果:
python面向对象(part2)--封装_低耦合_05

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

属性​​@property​

  • 定义及调用

python面向对象(part2)--封装_下划线_06

  • 说明

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

案例(可读、可写)

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

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

代码第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)--封装_下划线_08

在代码中,我们设置变量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)--封装_python_09

代码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)--封装_低耦合_10

代码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)--封装_下划线_11


我们改一下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)--封装_python_12
报错了!

接下来,我们把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)--封装_低耦合_13
嗯!报错了。

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

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)--封装_低耦合_14

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

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

①当执行【​​b01 = Bunny("小白")​​​】时:
python面向对象(part2)--封装_下划线_15

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

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

结果:
python面向对象(part2)--封装_低耦合_17

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