这一篇着重介绍面向对象编程的三大特征中的封装和继承;
封装
封装指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问,而只能通过该类所提供的方法来实现对内部信息的访问和操作;
封装的目的:
- 隐藏类的实现细节;
- 限制对类的不合理访问;
- 有利于保证对象信息的完整性;
- 便于修改,提高代码的可维护性;
那么具体怎么实现封装呢?
1.首先需要把属性和实现细节隐藏起来;
在类中,只要在属性的前面加两条下划线就表示该属性只能在内部使用,不能被外部访问和操作:
class Stu():
def __init__(self,name,age):
self.__name = name
self.__age = age
def info(self):
print(self.__name.title() + "'s age is " + str(self.__age) + '.')
这里注意 __xxx__这种变量名比较特殊,它是外部也可以访问的,还有 _xxx 这种变量名,它只有一个下划线,它虽然也可以被外部访问,但它的潜在意思是 虽然可以被外访问,但不要轻易访问;
另外要提一点,就算是__name、__age 这种变量也不是外部绝对就无法访问的,不能访问是因为Python解释器把 __name 改成了 _Stu__name,因此仍然可以用_Stu__name来访问,甚至来进行赋值和修改;但不同版本的Python解释器可能改动也不一样,因此轻易还是不要这么干;
除了属性之外,你也可以在方法名的前面加两条下划线,那么Python默认这个方法就是被隐藏的,如果你在外部直接调用就会报错,但同样与属性一样,可以通过其它方式调用;
总结来说就是,Python并没有提供真正意义上的隐藏机制,究竟该怎么搞还是看你;
2.提供可以访问和操作内部信息的方法
a.property()函数
这里先举一个例子:
class Rectangle(): #定义一个长方形类
def __init__(self,width,height):
self.__width = width
self.__height = height
def setsize(self,size):
self.__width,self.__height = size
def getsize(self):
return self.__width,self.__height
def delsize(self):
self.__width,self.__height = 0,0
如上,尽管我们不能直接访问类中的实例属性,但我们通过getwidth,setsize等方法依然能够获得我们想要的数据并对之进行操作;
但是你可能会觉得这种方法过于麻烦,那么你也可以使用Python提供的property()函数,property()函数的基本使用语法如下:
属性名 = property(fget=None, fset=None, fdel=None, doc=None)
四个参数分别对应读、写、删函数以及说明文档,在具体使用时Python会根据你传入的参数多少来决定property属性究竟具备多少功能;
看一个具体使用的例子:
class Rectangle(): #定义一个长方形类
def __init__(self,width,height):
self.__width = width
self.__height = height
def setsize(self,size):
self.__width,self.__height = size
def getsize(self):
return self.__width,self.__height
def delsize(self):
self.__width,self.__height = 0,0
size = property(getsize,setsize,delsize,'这是一个长方形')
这个类与上一个类唯一不同之处在于它多了一个size属性,我们往size属性里面传入了四个参数,这意味着该属性具备读写删以及提供说明文档的功能,接下来我们就可以借助该属性对类中的实例属性进行相关操作了;示例如下:
print(Rectangle.size.__doc__) #输出说明文档
rec = Rectangle(5,6) #读操作
print(rec.size)
rec.size = 7,9 #写操作
print(rec.size)
del rec.size #删除操作
print(rec.size)
输出:
这是一个长方形
(5, 6)
(7, 9)
(0, 0)
<?>这里有一个小疑问,如果setsize()函数中的参数设置为(self,w,h),意思是分开赋值,之后再使用property下的size属性进行写操作时就会出错,错误原因是:
TypeError: setsize() missing 1 required positional argument: 'h'
这个错误我百度了好久也没有找到结果,先埋在这里,留待以后再遇到类似情况再挖出来填上;
b.@property装饰器
Python不仅提供了property()函数,还提供了@property装饰器,通过@property装饰器,可以将类中的方法当作属性去调用,也就是说,在使用方法时不需要再加小括号;
仍以上面的Rectangle类说明:
class Rectangle():
def __init__(self,width,height):
self.__width = width
self.__height = height
@property
def getsize(self):
return self.__width,self.__height
我们用@property修饰getsize()方法,使getsize()方法变成了getsize属性的getter()方法。这里注意如果类中只包含这一个方法,那么该属性将是一个只读属性;
之后就可以直接利用该属性访问:
rec = Rectangle(5,6)
print(rec.getsize)
此外除了@property装饰器之外,还有setter以及deleter装饰器,它们分别提供修改属性以及删除属性的功能,用法如下:
@方法名.setter
@方法名.deleter
继承
编写类时,并非总要从空白开始,如果你要编写的类是另一个现有的类的特殊版本,可使用继承;
一个类继承另一个类时,它将获得原有类的所有属性和方法,包括被隐藏起来的,原有的类称为父类,继承得来的类称为子类;子类不仅继承得到了父类的所有属性和方法,还可以定义自己的属性和方法;
1.子类的创建
使用super()函数:
class Animal():
def __init__(self,name):
self.name = name
def run(self):
print(self.name.title() + " is running.")
class Dog(Animal):
def __init__(self,name):
super().__init__(name)
my_dog = Dog('Tom')
my_dog.run() #Tom is running.
如上,我首先创建了一个父类Animal,然后让子类Dog继承Animal,继承时用到了一个特殊函数:super(),具体实现方法代码中已经详细地给了出来;
这里只需要注意子类Dog后的括号里面需要写入父类名称Animal;
super()函数会调用父类的__init__()方法,将父类与子类连接起来,在单继承时你可以不需要考虑它,即把它省略:
class Dog(Animal):
pass
class Cat(Animal):
pass
这对继承并不会造成影响,上例定义了两个类Dog和Cat,它们都继承了父类的所有属性和方法;
另外,我们在继承的子类中也可以定义新的属性和方法,这里不再举例;
2.多继承
大多数面向对象的编程语言(除C++)都只支持单继承,Python虽然在语法上明确支持多继承,但多继承增加了编码复杂度,容易出现错误,一般情况下还是不要使用;
使用多继承非常简单,在子类名后的括号内添加多少个父类,它就会继承多少个;
这里要注意的就是,如果一个方法在父类1里面有,在父类2里面也有,那么怎么办呢?子类到底该执行哪个?
这就需要格外留意子类圆括号内父类的顺序:
class C_child(F1,F2,F3)
如上子类C_child,如果F1,F2,F3里面都有名称重复的方法,那么在子类使用该方法时又未指定,那么排在前面的父类方法会屏蔽后面的重名方法,排列顺序就是 你指定的 F1,F2,F3这种顺序;
3.MixIn设计模式
MixIn设计模式本质上就是一种多继承,MixIn的目的就是给一个类增加多种功能,因此在设计时可以考虑多继承来组合出我们想要的类,而不是设计多层次的复杂的设计关系;
4.父类方法重写
当父类中某个方法并不是你想要的方法时,你可以对该方法进行重写,即在子类中定义一个和该方法重名的方法,然后设计出你想要的方法,设计完成后,子类中的方法会覆盖掉父类中的重名方法,这种操作叫做方法重写;
但如果你想在子类中调用父类中被重写的方法时该怎么办呢?由于该方法已被重写,因此调用出的总是子类中已被重写的方法,这时可以用 父类名.方法 的方式调用父类中的该方法;
5.super()函数与多继承
如果子类继承了多个父类,那么它究竟该继承哪一个父类中的构造方法呢?如果不使用super(),那么默认继承排在第一个位置的父类继承,这当在父类排列的不严谨时或者其它情况下都很容易出现错误,因此这时就需要重写父类构造方法,并通过super()函数调用;
部分内容(不限于本文)参考自: