Python - 面向对象编程:类与继承
在介绍类(class)之前,先引入两个概念,即编程的两种方式:
-
面向过程编程(Procedure Oriented Programing,POP)
面向过程是一种以过程为中心的编程思想,该方法以正在发生的事件为主要目标进行编程。面向过程编程就是拆解出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了,是一种自顶向下的编程方式。C语言就是一种面向过程的编程语言,其重点是函数,执行过程就是一个main函数逐个地调用子函数。
举个栗子:小明上学的过程大概可以分解为:①起床;②洗漱;③吃早餐;④乘公交;⑤到学校。将上述过程逐个编写函数实现,然后逐一调用即可。
优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源。对于单片机、嵌入式开发、 Linux、Unix等一般采用面向过程开发,性能是最重要的因素。
缺点:可维护性、复用性、扩展性较差。 -
面向对象编程(Object Oriented Programing,OOP)
面向对象是一种抽象化的编程方法,通过将具有相同的数据结构(属性)和行为(方法)的对象抽象成类,然后封装在一起。面向对象相对于面向过程是一种更高层的封装,根据性质,在一个类中封装多个方法。即面向对象的编程方法将同一类的事物对象为一个类,不同的类具有不同的属性和方法,可以通过实例化创建不同的对象,然后给对象赋予一些新的属性和方法,然后让每个对象去执行自己的方法,问题得到解决。
举个栗子:飞机有很多种,对于客机来说,它们有很多共同的性质,比如都有机翼、引擎,形状相似,但也有差异,比如颜色、大小、引擎数量等。根据它们的共同性质,抽象出一个基本的图纸(类),根据图纸,经过细微修改,可以制造出不同的飞机(实例化)。
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护。
缺点:相对于面向过程的编程方法,性能较低。
类(class)和对象(object)
在前面介绍面向对象编程的时候,已经大致介绍了类是什么:类是用来描述具有相同的属性和方法的对象的集合,定义了该集合中每个对象所共有的属性和方法,类的属性一般以变量(类变量、实例变量)的形式来声明,类的方法一般以函数的形式声明。类是面向对象程序设计中,实现信息封装的基础,是一种用户定义的引用数据类型,也称类类型。
类通过实例化可以创建不同的对象,对象中包括数据成员(类变量和实例变量)和方法(类中定义的函数)。
类具有三大特性,分别为:封装性、继承性、多态性。
数据成员:类变量、实例变量和局部变量
在类体中,根据变量定义的位置不同,以及定义的方式不同,类属性又可细分为以下 3 种类型:
-
类体中,类函数之外:此范围定义的变量,称为 类属性 或 类变量 ;
类变量的特点是,所有类的实例化对象都同时共享类变量,也就是说,类变量在所有实例化对象中是作为公用资源存在的。类方法的调用方式有 2 种,既可以使用类名直接调用,也可以使用类的实例化对象调用。
需要注意的一点是,类变量通常不作为实例变量使用。即修改类变量只能通过类名.变量
的形式修改,而不能使用实例名.变量
的形式修改,这样会将该变量修改为实例变量,将不再受到类变量的控制,即修改类变量将不再对该实例中的变量起作用。In [1]: class A(): # 定义A类 ...: class_var1 = 1 ...: class_var2 = 2 ...: In [2]: a = A() # 实例化A类 # 调用类变量(可以通过类名调用,也可以通过对象名调用) In [3]: a.class_var1, a.class_var2, A.class_var1, A.class_var2 Out[3]: (1, 2, 1, 2) # 通过类名修改类变量,则所有实例对象中的类变量都会被修改 In [4]: A.class_var1 = 11 In [5]: a.class_var1, a.class_var2, A.class_var1, A.class_var2 Out[5]: (11, 2, 11, 2) # 通过对象名修改类变量,会将该变量修改为实例变量,将不再受到类变量修改的控制,即使用对象名来进行操作,不是在给“类变量赋值”,而是定义新的实例变量。 In [6]: a.class_var1 = 111 In [7]: a.class_var1, a.class_var2, A.class_var1, A.class_var2 Out[7]: (111, 2, 11, 2) # a.class_var1被修改为实例变量之后,使用A.class_var1修改类变量,将不再对实例变量a.class_var1起作用 In [8]: A.class_var1 = 1111 In [9]: a.class_var1, a.class_var2, A.class_var1, A.class_var2 Out[9]: (111, 2, 1111, 2)
-
类体中,类函数内部:以“self.变量名”的方式定义的变量,称为 实例属性 或 实例变量 ;
实例变量指的是在任意类方法内部,以self.变量名
的方式定义的变量,其特点是只作用于调用方法的对象。另外,实例变量只能通过对象名访问,无法通过类名访问。 -
类体中,类函数内部:以“变量名=变量值”的方式定义的变量,称为 局部变量 。
通常情况下,定义局部变量是为了所在类方法功能的实现。需要注意的一点是,局部变量只能用于所在函数中,函数执行完成后,局部变量也会被销毁。
此外,可以通过类名.变量
的形式来添加新的类变量,可以通过对象名.变量
的形式来添加新的实例变量。
方法:实例方法、类方法和静态方法
方法:类中定义的函数,和类属性一样,类方法也可以进行更细致的划分,具体可分为实例方法、类方法和静态方法。区分这 3 种类方法是非常简单的,即采用 @classmethod
修饰的方法为类方法;采用 @staticmethod
修饰的方法为静态方法;不用任何修改的方法为实例方法,其中 @classmethod
和 @staticmethod
都是函数修饰器。
-
实例方法:通常情况下,在类中定义的方法默认都是实例方法,并且类的构造方法
def __init__(self):
理论上也属于实例方法,只不过它比较特殊。
实例方法最大的特点就是,它必须要包含一个self
参数,用于绑定调用此方法的实例对象,但是需要注意的是self
参数的命名不是固定的(可以随意命名),只是 Python 程序员约定俗成的习惯而已。实例方法通常会用类对象直接调用,例如:In [1]: class Method: ...: def hello(self): ...: print("hello world") ...: In [2]: method = Method() # 通过对象名直接调用方法 In [3]: method.hello() hello world # Python 也支持使用类名调用实例方法,但此方式需要手动给 self 参数传值。 In [4]: Method.hello(method) hello world
-
类方法:类方法和实例方法相似,它最少也要包含一个参数,只不过类方法中通常将其命名为
cls
,Python 会自动将类本身绑定给cls
参数(注意,绑定的不是类对象)。也就是说,我们在调用类方法时,无需显式为cls
参数传参。和self
一样,cls
参数的命名也不是固定的(可以随意命名),只是 Python 程序员约定俗称的习惯而已。类方法推荐使用类名直接调用,当然也可以使用实例对象来调用(不推荐)。
类方法需要使用@classmethod
修饰符进行修饰,否则python会自动将其作为实例方法进行处理(在未指定@classmethod
时,cls
与self
作用相同)。 -
静态方法:静态方法,其实就是函数,只不过他们的作用空间不同,静态方法定义在类空间(类命名空间)中,而函数则定义在程序所在的空间(全局命名空间)中。静态方法没有类似
self
、cls
这样的特殊参数,因此 Python 解释器不会对它包含的参数做任何类或对象的绑定。也正因为如此,类的静态方法中无法调用任何类属性和类方法。静态方法需要使用@staticmethod
修饰。
方法重写
方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。方法的重写也即重新定义一个与所继承的类中的某一方法重名的方法。
注意:在重写类的构造方法 def __init__(self):
时,需要使用super()
调用父类中的构造方法,因为__init__
中定义了类中所必须的变量等内容。
类的继承
继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
在Python中,有两个最基本的类:
- object类:也称为基类;
- type类:也称为元类。
我们知道,实例对象是由类来创建,那么类又是由什么来创建的呢? 答案就是元类。即元类创建类,类创建对象。
- 元类继承了基类,基类不继承任何类;
- 元类创建了所有类。
>>> object.__bases__
()
>>> type.__bases__
(<class 'object'>,)
在python2.x中,不继承object类的称之为经典类,继承了object类的称之为新式类。在python3.x中,创建类时,如果不继承用户自定义的类,那么该类会自动继承基类(object类),即是所有的自定义类都会继承object类,也即默认创建的所有类都是新式类,object类里面内置了许多基本函数(方法)。
经典类和新式类在方法解析顺序(method Resolution Order,MRO)上有所区别,由于在python3.x中已经弃用了经典类,全部采用新式类,所以此处不再赘述。
注意:关于元类与基类,了解即可,无需深究。