目录
1. 面向对象程序设计思想
面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
在设计之初,Python 就被设计成支持面向对象的编程语言,因此 Python 完全能以面向对象的方式编程。而且 Python 的面向对象比较简单,它不像其他面向对象语言提供了大量繁杂的面向对象特征,它致力于提供简单、够用的语法功能。
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。
在 Python 中创建一个类和对象很容易,Python 支持面向对象的三大特征:封装、继承和多态,子类继承父类同样可以继承到父类的变量和方法。
2. 面向对象相关术语
(1)、类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
(2)、方法:类中定义的函数。
(3)、类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
(4)、数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
(5)、方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
(6)、局部变量:定义在方法中的变量,只作用于当前实例的类。
(7)、实例变量:在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
(8)、继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
(9)、实例化:创建一个类的实例,类的具体对象。
(10)、对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
和其它编程语言相比,Python 在尽可能不增加新的语法和语义的情况下加入了类机制。
Python中的类提供了面向对象编程的所有基本功能:类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法,方法中可以调用基类中的同名方法。
对象可以包含任意数量和类型的数据。
3. 类与对象的基本使用
3.1 定义一个类
面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记,类是抽象的模板,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。在Python中,定义类是通过class关键字:
#!/usr/bin/python3 class 类名: 类属性... 类方法... class MyClass:i = 12345 |
注意,类中属性和方法所在的前后顺序没有任何影响,且各成员之间可以相互调用。
Python 的类定义有点像函数定义,都是以冒号(:)作为类体的开始,以统一缩进的部分作为类体的。区别只是函数定义使用 def 关键字,而类定义则使用 class关键字。
Python 的类定义由类头(指 class 关键字和类名部分)和统一缩进的类体构成,在类体中最主要的两个成员就是属性和方法。如果不为类定义任何属性和方法,那么这个类就相当于一个空类,如果空类不需要其他可执行语句,则可使用 pass 语句作为占位符。
比如,下面这样的类定义是允许的:
class Wbyq: pass |
3.2 类对象的创建
使用 class 语句只能创建一个类,而无法创建类的对象,因此要想使用已创建好的类,还需要手动创建类的对象,创建类对象的过程又称为类的实例化。
示例代码: 对已创建的类进行实例化
#!/usr/bin/python3 #创建一个类 class MyClass: data=666 def funcname(self, parameter_list): print("传入的参数:", parameter_list) #实例类:创建对象 wbyq=MyClass() |
3.3 类对象的使用
使用已创建好的类对象访问类中实例变量的语法格式:对象名.变量名
使用类对象调用类中方法的语法格式:对象名.方法名(参数)
注意,对象名和变量名以及方法名之间用点 "." 连接。
(1)、示例: 访问类中成员
#!/usr/bin/python3 #创建一个类 class MyClass: data=666 def funcname(self, parameter_list): print("传入的参数:", parameter_list) #实例类:创建对象 wbyq=MyClass() print("data=", wbyq.data) #访问类的成员,使用.点号 wbyq.funcname("hello world.") |
结果:data= 666
传入的参数: hello world.
Python不仅支持访问类中已经定义的变量,还支持为已创建好的对象动态增加变量。注意: 增加和删除变量只是针对对象本身,对类模板没有影响。
(2)、示例:动态添加变量与删除
#!/usr/bin/python3 #创建一个类 class MyClass: data1=666 data2=888
#实例类:创建对象 wbyq=MyClass() wbyq.data1=123 wbyq.data2=456 wbyq.data3=789 print("data1=",wbyq.data1) print("data2=",wbyq.data2) print("data3=",wbyq.data3) #实例类:创建对象 xl=MyClass() print("data1=",xl.data1) print("data2=",xl.data2) #print("data3=",xl.data3) #无法访问,因为类里没有data3这个变量 # 删除wbyq对象的name实例变量 del wbyq.data1 del wbyq.data2 del wbyq.data3 #删除变量之后无法再访问 #print("data1=",wbyq.data1) #print("data2=",wbyq.data2) #print("data3=",wbyq.data3) |
3.4 类方法的self参数
Python 要求,类方法(构造方法和实例方法)中至少要包含一个参数,但并没有规定此参数的名称(完全可以叫任意参数名),上面代码里将类方法的第一个参数命名为 self,只是 Python 程序员约定俗成的一种习惯,这会使程序具有更好的可读性,Python 类方法中的 self 参数就相当于 C++ 中的 this 指针。
同一个类可以产生多个对象,当某个对象调用类方法时,该对象会把自身的引用作为第一个参数自动传给该方法。
对于构造方法来说,self 参数(第一个参数)代表该构造方法正在初始化的对象,程序在调用实例方法和构造方法时,不需要为第一个参数传值。
示例: 区分是哪个对象调用了类的方法
#!/usr/bin/python3 #创建一个类 class MyClass: data=666 def func(self): print("调用func方法的对象是:", self) #实例类:创建对象 wbyq1=MyClass() wbyq1.func() #实例类:创建对象 wbyq2=MyClass() wbyq2.func() |
结果:
调用func方法的对象是: <__main__.MyClass object at 0x000001A02C9EA280>
调用func方法的对象是: <__main__.MyClass object at 0x000001A02CB33640>
(2)、示例: 使用self访问类属性成员
#!/usr/bin/python3 #创建一个类 class MyClass: data1=666 data2=888 def func(self): print("data1=", self.data1) #使用self访问类属性成员 print("data2=", self.data2) #使用self访问类属性成员 #实例类:创建对象 wbyq=MyClass() wbyq.func() |
(3)、示例: self完全可以设置为任意参数名,self名称只是 Python程序员约定的一种习惯
#!/usr/bin/python3 #创建一个类 class MyClass: data1=666 data2=888 def func(data): #可以任意命名,不一定非的是self print("data1=", data.data1) #使用self访问类属性成员 print("data2=",data.data2) #使用self访问类属性成员 #实例类:创建对象 wbyq=MyClass() wbyq.func() |
3.5 __init__()类构造方法
在创建类时,可以手动添加一个 __init__() 方法,该方法是一个特殊的类实例方法,称为构造方法(或构造函数)。
构造方法用于创建对象时使用,每当创建一个类的实例对象时,Python 解释器都会自动调用它。
Python 类中,添加构造方法的语法格式:
def __init__(self,...): 代码块 |
注意,此方法的方法名中,开头和结尾各有 2 个下划线,且中间不能有空格。Python 中很多这种以双下划线开头、双下划线结尾的方法,都具有特殊的意。
另外,__init__() 方法可以包含多个参数,但必须包含一个名为 self 的参数,且必须作为第一个参数。也就是说,类的构造方法最少也要有一个 self 参数。
如果开发者没有该类定义任何构造方法,那么 Python 会自动为该类创建一个只包含 self 参数的默认的构造方法。示例代码:
#!/usr/bin/python3 #创建一个类 class MyClass1: def __init__(self): print("调用构造函数") #实例类:创建对象 wbyq1=MyClass1() #创建对象的时候,自动调用方法__init__() #创建一个类 class MyClass2: def __init__(self,list): print("构造函数收到的形参:",list) #实例类:创建对象 wbyq2=MyClass2("hello world") |
结果:调用构造函数
构造函数收到的形参: hello world
3.6 类变量和实例变量(类属性和实例属性)
无论是在类中定义的属性还是方法,在类的外部,都无法直接调用它们,完全可以把类看做是一个独立的作用域(称为类命名空间),则类属性其实就是定义在类命名空间内的变量(类方法其实就是定义的类命名空间中的函数)。
根据定义属性的位置不同,类属性又可细分为类属性(类变量)和实例属性(实例变量)。
(1)、类变量指的是定义在类中,在类方法外的变量。
类变量的特点是:所有类的实例化对象都可以共享类变量的值,即类变量可以在所有实例化对象中作为公用资源,当一个对象修改了类变量的值,其他对象访问类变量的值也会改变。
在类的方法中,访问类变量可以使用,类名.变量名的方式进行访问或者使用self (对象引用)访问。示例代码:
#!/usr/bin/python3 #创建一个类 class MyClass: data=123 def func1(self,list): MyClass.data=list #修改类变量的值 def func2(self): print("data=", MyClass.data) #打印类变量的值 #实例类:创建对象 wbyq1=MyClass() wbyq2=MyClass() #访问类成员 wbyq1.func2() wbyq2.func2() wbyq1.func1(888) wbyq1.func2() wbyq2.func2() |
(2)、实例变量指的是定义在类的方法中的属性,它的特点是: 只作用于调用方法的对象。实例变量只能通过对象名访问,无法通过类名直接访问。
Python允许通过对象访问类变量,但无法通过对象修改类变量的值。因为,通过对象修改类变量的值,不是在给“类变量赋值”,而是定义新的实例变量。示例代码:
#!/usr/bin/python3 #创建一个类 class MyClass: def func1(self,list): data=123 self.data=list #无法修改类变量的值,相当于定义新的实例变量 print("data=",MyClass.data) #访问新的实例变量 def func2(self): print("data=",MyClass.data) #打印类变量的值 #实例类:创建对象 wbyq=MyClass() wbyq.func1(666) wbyq.func2() |
3.7 实例方法、静态方法和类方法
类属性可细分为类属性和实例属性,类中的方法也可以分为类方法、实例方法和静态方法。
(1)、Python类实例方法
通常情况下,在类中定义的方法默认都是实例方法。
实例方法最大的特点就是,它最少也要包含一个 self 参数,用于绑定调用此方法的实例对象,实例方法通常会用类对象直接调用,当然也可以用类名调用,使用类名调用时,方法第一个参数需要显式传递(对象)。示例代码:
#!/usr/bin/python3 #创建一个类 class MyClass: def funcn(self, parameter_list): print(parameter_list) #实例类:创建对象 wbyq=MyClass() #通过对象名调用实例方法 wbyq.funcn("通过对象名调用实例方法") #通过类名调用实例方法 MyClass.funcn(wbyq,"通过类名调用实例方法") #第一个参数需要显式传递 |
(2)、Python类方法
Python类方法和实例方法相似,它最少也要包含一个参数,只不过,类方法中通常将其命名为 cls,且 Python 会自动将类本身绑定给 cls 参数(而不是类对象)。因此,在调用类方法时,无需显式为 cls 参数传参,和 self 一样,cls 参数的命名也不是规定的(可以随意命名),只是 Python 程序员约定俗称的习惯而已。
类方法定义和实例方法最大的不同在于,类方法需要使用@classmethod进行修饰:示例代码:
#!/usr/bin/python3 #创建一个类 class MyClass: @classmethod def funcn(cls,parameter_list): print("类方法:",parameter_list) #使用类名调用类方法,第一个参数不需要显示传递 MyClass.funcn("hello world.") |
注意,如果没有 @classmethod,Python 解释器会将funcn ()方法认定为实例方法,而不是类方法。
(3)、Python类静态方法
静态方法,其实就是函数,和函数唯一的区别是,静态方法定义在类这个空间(类命名空间)中,而函数则定义在程序所在的空间(全局命名空间)中。
静态方法没有类似 self、cls 这样的特殊参数,因此 Python 解释器不会对它包含的参数做任何类或对象的绑定,也正是因为如此,此方法中无法调用任何类和对象的属性和方法。静态方法需要使用@staticmethod修饰。
示例代码:
#!/usr/bin/python3 #创建一个类 class MyClass: @staticmethod def func(parameter_list): print(parameter_list) #使用类名调用类方法 MyClass.func("使用类名调用类方法") #使用对象调用静态方法 wbyq=MyClass() wbyq.func("使用对象调用静态方法") |
结果:使用类名调用类方法
使用对象调用静态方法
4. 类封装机制
封装(Encapsulation)是面向对象的三大特征之一(另外两个是继承和多态),它指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。
封装机制保证了类内部数据结构的完整性,因为使用类的用户无法直接看到类中的数据结构,只能使用类允许公开的数据,很好地避免了外部对内部数据的影响,提高了程序的可维护性。
对一个类或对象实现良好的封装,可以达到以下目的:
(1)、隐藏类的实现细节。
(2)、让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里加入控制逻辑,限制对属性的不合理访问。
(3)、可进行数据检查,从而有利于保证对象信息的完整性。
(4)、便于修改,提高代码的可维护性。
为了实现良好的封装,需要从以下两个方面来考虑:
(1)、将对象的属性和实现细节隐藏起来,不允许外部直接访问。
(2)、把方法暴露出来,让方法来控制对这些属性进行安全的访问和操作。
实际上封装有两个方面的含义:把该隐藏的隐藏起来,把该暴露的暴露出来。
Python 并没有提供类似于其他语言的 private 修饰符,如果想要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__。
在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。
示例代码: 定义私有变量
#!/usr/bin/python3 #创建一个类 class MyClass: #定义私有变量,外部无法使用 __data=888 def funcname(self): print("__data=", self.__data) print("__data=", MyClass.__data) #创建对象 wbyq=MyClass() wbyq.funcname() #print(wbyq.__data)#无法访问:AttributeError: 'MyClass' object has no attribute '__data' |
使用私有变量确保了外部代码不能随意修改对象内部的状态,通过访问限制的保护,代码更加健壮。
如果又要允许外部代码修改私有变量,可以增加一个方法间接访问:
#!/usr/bin/python3 #创建一个类 class MyClass: __data=888 #定义私有变量 def func(self, data): if 50<data<100: MyClass.__data=data #修改私有变量 else: raise ValueError('范围错误') #创建对象 wbyq=MyClass() wbyq.func(60) |
在方法中间接修改私有变量,可以对参数做检查,避免传入无效的参数。
5. 类继承机制
继承是面向对象的三大特征之一,也是实现代码复用的重要手段。
继承经常用于创建和现有类功能类似的新类或者是新类只需要在现有类基础上添加一些成员(属性和方法)。
Python 中,实现继承的类称为子类,被继承的类称为父类(也可称为基类)。
子类继承父类的语法是:在定义子类时,将多个父类放在子类之后的圆括号里。语法格式如下:
class 类名(父类1, 父类2, ...): 类定义部分 |
Python 的继承是多继承机制,一个子类可以同时拥有多个直接父类。
定义子类,只需在原来的类定义后增加圆括号,并在圆括号中添加多个父类,表明该子类继承了这些父类。如果在定义一个 Python 类时,并未显式指定这个类的直接父类,则这个类默认继承 object 类,object 类是所有类的父类,要么是直接父类,要么是间接父类。
示例代码:
#!/usr/bin/python3 class MyClass1: def func1(self, parameter_list): print("我是父类1",parameter_list) class MyClass2: def func2(self, parameter_list): print("我是父类2",parameter_list) class MyClass3(MyClass1,MyClass2): def func3(self, parameter_list): print("我是子类",parameter_list) #创建对象 wbyq=MyClass3() wbyq.func1("hello world.") #从父类继承的方法 wbyq.func2("hello world.") #从父类继承的方法 wbyq.func3("hello world.") |
需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python会从左至右搜索,就是说方法在子类中未找到时,从左到右查找父类中是否包含方法,如果没有找到就抛出异常。
示例代码:
#!/usr/bin/python3 class MyClass1: def func(self, parameter_list): print("我是父类1:",parameter_list) class MyClass2: def func(self, parameter_list): print("我是父类2:",parameter_list) class MyClass3(MyClass1,MyClass2): pass #def func(self, parameter_list): # print("我是子类:",parameter_list) #创建对象 wbyq=MyClass3() wbyq.func("hello world.") |
Python 子类继承父类构造函数说明:
(1)、子类需要自动调用父类的方法:子类不重写__init__()方法,实例化子类后,会自动调用父类的__init__()的方法。
(2)、子类不需要自动调用父类的方法:子类重写__init__()方法,实例化子类后,将不会自动调用父类的__init__()的方法。
(3)、子类重写__init__()方法又需要调用父类的方法:使用super关键词。
语法: super(子类,self).__init__(参数1,参数2,....)
示例:
class MyClass2(MyClass1):
def __init__(self, name):
super(MyClass2, self).__init__(name)
6. 父类方法重写
当子类和父类都存在相同的方法时,子类的方法就会覆盖父类方法,在代码运行的时候,总是会调用子类的方法。这种子类包含与父类同名的方法的现象被称为方法重写(Override),也被称为方法覆盖。可以说子类重写了父类的方法,也可以说子类覆盖了父类的方法。
(python中可以忽略多态,Python原生就是多态,print(arg) #形态不固定,字符串,数字...都行)
示例代码: 父类方法重写
#!/usr/bin/python3 class MyClass1: def func(self, parameter_list): print("我是父类", parameter_list) class MyClass2(MyClass1): def func(self, parameter_list): print("我是子类", parameter_list) #创建对象 wbyq=MyClass2() wbyq.func("hello world.") |
结果:我是子类 hello world.
如果父类方法的功能不能满足需求时,就可以在子类重写父类的方法。
如果子类重写了父类的方法之后,想要再次调用父类的方法,可以使用super函数。super() 函数是用于调用父类(超类)的一个方法。
示例:
#!/usr/bin/python3 class MyClass1: def func(self, parameter_list): print("我是父类1:",parameter_list) class MyClass2: def func(self, parameter_list): print("我是父类2:",parameter_list) class MyClass3(MyClass1,MyClass2): def func(self, parameter_list): super().func(parameter_list) #调用父类 #创建对象 wbyq=MyClass3() wbyq.func("hello world.") |
结果:我是父类1: hello world.