声明:本内容非盈利性质,也不支持任何组织或个人将其用作盈利用途。本内容来源于参考书或网站,会尽量附上原文链接,并鼓励大家看原文。侵删。
2.2 封装
2.2.1 关于封装(参考链接:https://blog.csdn.net/qq_44034384/article/details/106470717)
(1)封装的含义
封装是实现面向对象程序设计的第一步,封装就是将数据或函数等集合在一个个的单元中(我们称之为类)。被封装的对象通常被称为抽象数据类型。
前面我们已经定义的类,即将属性封装在了类中,但是我们并没有实现严格意义上的封装,因为此时的类属性是可以被外部随意修改的。比如:
class Dog:
petName = 'dog' # 类中定义了nickname属性之后才能使‘print(cls.nickname)’语句不报错
def __init__(self, nickname):
self.nickname = nickname
def run(self):
print('{}在院子里走来走去!'.format(self.nickname))
@classmethod # 定义类方法通过decorater来添加
def test1(cls): # 类方法的系统默认参数为cls即class的简写
print(cls)
print(cls.petName)
# print(cls.nickname) # 这个语句是会报错的,因为nickname是对象的属性,而非类的属性
@staticmethod
def test2(): # 静态方法没有系统指定的默认参数
print('-->静态方法')
# print(self.petName) 静态方法同样不能调用self对象
print(Dog.petName) # 静态方法不能通过cls调用类属性,但可以直接通过类名调用类属性
d = Dog("大黄")
print(d.nickname)
d.nickname = "小黄"
print(d.nickname)
在实际使用中,有些属性可以被随时修改,但有些属性不能随意修改,所以上面定义类的方式不能保证属性被误修。因此,只将属性抽象成一个类而不做其他操作,显然不能称为严格的封装。
严格的封装还需要做一个操作,就是将属性私有化。而对于私有化属性修改,则要交给特定的方法去执行,称为方法公开。因此,完全意义上的封装包含三个方面:
- 抽象为类:即将属性写在一个类中;
- 属性私有:将所有的属性都设为私有;
- 方法公开:根据对属性的要求决定如何公开,属性的公开由方法来完成。(随时修改的属性可以“读写”,有些属性“只读”)
(2)封装的作用
封装的作用在于保护或者防止代码(数据)被我们无意中破坏。在面向对象程序设计中数据被看作是一个中心的元素并且和使用它的函数结合的很密切,从而保护它不被其它的函数意外的修改。
封装有很多好处:
- 拒绝直接调用声明字段,保护内部数据,更安全;
- 在编程中可达到缓存的效果,执行效率高;(因为类一旦被定义,其基本结构已经加载在内存中了,不需要再逐个语句地初始化)
- 重复调用,避免代码冗余,程序编写效率高。(一个类的结构可以被多个对象重复使用,有利于程序直接建立在更高级的数据结构上)
在2.1中我们已经学习了如何抽象成类,在本节我们要在此基础上了解如何进行属性私有和方法公开。
2.2.2 属性私有
java和C++中有访问权限修饰符来控制属性及方法的私有及公开程序,而python中没有这些访问权限修饰符。python中只有__
用来作为属性私有的符号。如下:
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
self.__score = 59 # 以--开头的变量就是私有的
def __str__(self): # 自定义__str__方法,打印对象时按自定义格式输出
return '姓名:{},年龄:{},考试分数:{}'.format(self.name, self.age, self.__score)
#创建对象,并打印
y = Student('yupeng', 18)
print(y) # 结果为“姓名:yupeng,年龄:18,考试分数:59”
# 尝试修改公有的变量age
y.age = 21
print(y.age) # 以这种方式调用变量发现变量值已经改变了
print(y) # 打印对象,发现age确实改变了
# 像修改公有变量那样,尝试修改私有变量__score
y.__score = 95 # 这样调用其实访问的不是Student中的实例变量,而是定义了一个新的变量,下面的结果会反映这个问题;
print(y.__score) # 打印发现结果是95,但这并不意味着对象中的属性已经改变了;
print(y) # 使用__str__方法打印对象,发现结果是“姓名:yupeng,年龄:21,考试分数:59”,说明__score属性并没有改变。因此私有变量不能通过常规方式访问及修改
# dir()函数可以查看类或对象中自定义与自带的attribute
print(dir(Student)) # 打印结果为“['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']”,如果dir()中的参数是类,则打印结果中没有显示实例属性
print(dir(y)) # 打印结果为“['_Student__score', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__score', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'name']”,如果打印的是对象,则其中会显示实例属性。在这个结果中我们会看到_Student__score和__score两个结果,因此符号私有化的本质是将我们定义的变量换了一个名称,在前面加上了‘__<类名>’。因此,我们只要用底层修改后的名称也是可以访问并修改私有属性的。
# 通过修改后的变量名访问及修改私有变量
y._Student__score = 95
print(y._Student__score) # 访问发现变量被修改了,值为95
print(y) # 打印结果为“姓名:yupeng,年龄:21,考试分数:95”,结果显示变量确实被修改了
符号私有化的本质是将变量名修改,加上了‘__<类名>’的前缀,而非真正意义上禁止了访问及修改。
2.2.3 方法公开
实际中,我们一般不支持随意修改对象的属性,因此对象的属性都是要做私有化的。但是访问及修改时,不能采用加‘__<类名>’的方式。因此我们需要将那些需要修改的私有方法进行公开。
(1)定义普通实例方法进行公开
公开的方式之一是在类中定义成员函数,进行公开:
class Student:
def __init__(self, name, age):
self.__name = name
self.__age = age
self.__score = 59 # 以--开头的变量就是私有的
def __str__(self): # 自定义__str__方法,打印对象时按自定义格式输出
return '姓名:{},年龄:{},考试分数:{}'.format(self.__name, self.__age, self.__score)
# 可以定义公有化的set函数修改私有化属性
def setScore(self, score):
if score > 0 and score <= 100:
self.__score = score
else:
print('分数输入不正确!')
# 可以定义公有化的get函数访问私有化属性的值
def getScore(self):
return self.__score
y = Student('yupeng', 18)
print(y)
y.setScore(95)
print(y.getScore())
这种方式用硬编码的方式创建了getter与setter方法,与java中是类似的。
(2)使用@property装饰的实例方法进行公开
@property的作用是将一个类中的成员函数变为类的一个属性。如下:(参考链接:https://zhuanlan.zhihu.com/p/64487092)
class DataSet(object):
@property
def method_with_property(self): ##含有@property
return 15
def method_without_property(self): ##不含@property
return 15
l = DataSet()
print(l.method_with_property) # 加了@property后,可以用调用属性的形式来调用方法,后面不需要加()。
print(l.method_without_property()) # 没有加@property , 必须使用正常的调用方法的形式,即在后面加()
私有化的本质就是修改变量名,如果使用@property修改的实例方法,不仅能起到公开的效果,还能起到私有化的效果(修改变量名)。如下:
class Student:
def __init__(self, name, age):
self.name = name
self.privateAge = age
self.__score = 59
@property
def trueScore(self):
return self.__score
@property
def trueAge(self):
return self.privateAge
@trueAge.setter # 相当于属性私有化中的set函数
def setAge(self, age):
if 0 < age < 100:
self.__age = age
else:
print('不在规定范围内!')
def __str__(self):
return '姓名:{},年龄:{},考试分数:{}'.format(self.name, self.privateAge, self.__score)
y = Student('yupeng', 18)
print(dir(Student)) # 打印结果为“['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'trueAge', 'trueScore']”
print(dir(y)) # 打印结果为“['_Student__score', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name', 'privateAge', 'trueAge', 'trueScore']”。_Student__score的属性依然是存在的,因此还是可以通过这种名称修改字段
print(y.trueScore) # 使用@property标记后,可以直接用调用属性的方式调用__score的getter方法
print(y.trueAge) # 公开属性也可以通过这种方式访问
y.setAge = 21 # @trueAge.setter与@property所标记的getter方法联用,表示创建一个setter方法。使用这种方式创建setter后,也可以使用修改属性的方式进行赋值
(3)定义一个属性私有、方法公开的类
以下类完整地定义了getter、setter和重写了str魔术方法。如下:
class Student:
def __init__(self, name, age, score):
self.__name = name
self.__age = age
self.__score = score
@property
def getName(self):
return self.__name
@property
def getAge(self):
return self.__age
@property
def getScore(self):
return self.__score
@getName.setter
def setName(self, name):
self.__name = name
@getAge.setter
def setAge(self, age):
if 0 < age < 100:
self.__age = age
else:
print('不在规定范围内!')
@getScore.setter
def setScore(self, score):
if 0 <= score <= 100:
self.__score = score
else:
print('不在规定范围内!')
def __str__(self):
return '姓名:{},年龄:{},考试分数:{}'.format(self.__name, self.__age, self.__score)