作者:billy
版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处
面向对象(Object Oriented)的英文缩写是 OO,它是一种设计思想。从 20 世纪 60 年代提出面向对象的概念到现在,它已经发展成为一种比较成熟的编程思想,并且逐步成为目前软件开发领域的主流技术。如我们经常听说的面向对象编程(Object Oriented Programming,即 OOP)就是主要针对大型软件设计而提出的。他可以使软件设计更加灵活,并且能更好地进行代码复用。
面向对象中的对象(Object),通常是指客观世界中存在的对象,这个对象具有唯一性,对象之间各不相同,各有各的特点,每个对象都有自己的运动规律和内部状态;对象和对象之间又是可以相互联系、相互作用的。另外,对象也可以是一个抽象的事物。例如,可以从圆形、正方形、三角形等图形中抽象出一个简单图形,简单图形就是一个对象,它有自己的属性和行为,图形中边的个数是它的属性,图形的面积也是它的属性,输出图形的面积就是它的行为。概括地讲,面向对象技术是一种从组织结构上模拟客观世界的方法。
在 Python 中,一切都是对象。即不仅是具体事物称为对象,字符串、函数等也都是对象。这说明 Python 天生就是面向对象的
类
类是封装对象的属性和行为的载体,反过来说,具有相同属性和行为的一类实体被称为类。在 Python 语言中,类是一种抽象概念,如定义一个大雁类(Geese),在该类中,可以定义每个对象共有的属性和方法;而一只要从北方飞往南方的大雁则是大雁类的一个对象(wildGeese),对象是类的实例。
面向对象程序设计的特点
面向对象程序设计具有三大基本特征:封装、继承、多态;
封装
封装是面向对象编程的核心思想,将对象的属性和行为封装起来,而将对象的属性和行为封装起来的载体就是类,类通常对客户隐藏其实现细节,就这事封装的思想。
采用封装思想保证了类内部数据结构的完整性,使用该类的用户不能直接看到类中的数据结构,而只能执行类允许公开的数据,这样就避免了外部对内部数据的影响,提高了程序的可维护性。
继承
继承是实现重复利用的重要手段,子类通过继承复用了父类的属性和行为的同时,又添加了子类特有的属性和行为。
多态
将父类对象应用于子类的特征就是多态。
在 Python 中,使用 class 关键字来定义一个类,语法如下:
class ClassName:
"类的帮助信息" # 类文档字符串
statement # 类体
ClassName:用于指定类名,一般使用大写字母开头,如果类名中包括两个单词,第二个单词的首字母也大写,这种命名方式也称为 “驼峰命名法”,这是惯例。当然,也可根据自己的习惯命名,但是一般推荐按照惯例来命名;
“类的帮助信息”:用于指定类的文档字符串,定义该字符串后,在创建类的对象时,输入类名和左侧的括号 ‘(’ 后,将显示该信息;
statement:类体,主要由类变量(或类成员)、方法和属性等定义语句组成。如果在定义类时,没想好类的具体功能,也可以在类体中直接使用 pass 语句代替;
class 语句本身并不创建该类的任何实例。所以在类定义完成之后,需要创建类的实例,即实例化该类的对象。
在创建类后,通常会创建一个 __init__() 方法,该方法是一个特殊的方法,类似 Java 语言中的构造方法。每当创建一个类的新实例时,Python 都会自动执行它。__init__() 方法必须包含一个 self 参数,并且必须是第一个参数。self 参数是一个指向实例本身的引用,用于访问类中的属性和方法,在方法调用时会自动传递实际参数 self。因此,当 __init__() 方法只有一个参数时,在创建类的实例时,就不需要指定实际参数了。
示例:
class Geese:
"大雁类"
def __init__(self, beak, wing, claw): # 构造方法
print("我是大雁类!我有以下特征:")
print(beak) # 输出喙的特征
print(wing) # 输出翅膀的特征
print(claw) # 输出爪子的特征
beak = "喙的基部较高,长度和头部的长度几乎相等" # 喙的特征
wing = "翅膀长而尖" # 翅膀的特征
claw = "爪子是噗状的" # 爪子的特征
wildGeese = Geese(beak, wing, claw) # 创建大雁类的实例
上述例子的运行结果为:
我是大雁类!我有以下特征:
喙的基部较高,长度和头部的长度几乎相等
翅膀长而尖
爪子是噗状的
类的成员主要由实例方法和数据成员组成。
所谓实例方法,是指在类中定义的函数。该函数是一种在类的实例上操作的函数。同 __init__() 方法一样,实例方法的第一个参数必须是 self,并且必须包含一个 self 参数。
而数据成员,是指在类中定义的变量,即属性。根据定义位置,又可以分为类属性和实例属性。类属性是指定义在类中,并且在函数体外的属性,类属性可以在类的所有实例之间共享值,也就是在所有实例化的对象中公用;实例属性是指定义在类的方法中的属性,只作用于当前实例中。
示例:
class Geese:
"大雁类"
neck = "脖子较长" # 类属性 - 脖子
wing = "振翅频率高" # 类属性 - 翅膀
leg = "腿位于身体的中心支点,行走自如" # 类属性 - 腿
number = 0 # 类属性 - 编号
def __init__(self): # 构造方法
Geese.number += 1 # 将编号 + 1
print("我是第 " + str(Geese.number) + " 只大雁,我属于雁类!")
def fly(self, state): # 定义飞行方法
self.myNeck = "脖子没有天鹅的长" # 实例属性 - 我的脖子
print("我有以下特征:")
print(self.myNeck) # 输出我的脖子的特征
print(self.neck) # 输出脖子的特征
print(self.wing) # 输出翅膀的特征
print(self.leg) # 输出爪子的特征
print(state) # 输出飞行特征
print()
list = []
wildGeese1 = Geese() # 创建大雁类的实例
wildGeese1.fly("飞行的时候,会排成人字!") # 调用实例方法
list.append(wildGeese1)
wildGeese2 = Geese() # 创建大雁类的实例
wildGeese2.neck = "脖子较短" # 通过实例来修改类属性
wildGeese2.wing = "翅膀是黑色的"
wildGeese2.leg = "是个小短腿"
wildGeese2.fly("飞行的时候,会排成一字!") # 调用实例方法
list.append(wildGeese2)
wildGeese3 = Geese() # 创建大雁类的实例
Geese.neck = "脖子不长不短" # 通过类名来修改类属性
Geese.wing = "翅膀上羽毛很多"
Geese.leg = "是个大长腿"
wildGeese3.fly("飞行的时候,会排成八字!") # 调用实例方法
list.append(wildGeese3)
list[0].myNeck = "我脖子的长度超过了天鹅的脖子"
print("第 1 只大雁的 myNeck 属性:", list[0].myNeck) # 实例属性,修改之后只有本实例自己有效,其他实例无变化
print("第 2 只大雁的 myNeck 属性:", list[1].myNeck)
print()
Geese.beak = "喙的基部较高,长度和头部的长度几乎相等" # 动态地为类和对象添加类属性,所有实例都会改变
print("第 2 只大雁的喙:", list[1].beak)
上述例子的运行结果为:
我是第 1 只大雁,我属于雁类!
我有以下特征:
脖子没有天鹅的长
脖子较长
振翅频率高
腿位于身体的中心支点,行走自如
飞行的时候,会排成人字!
我是第 2 只大雁,我属于雁类!
我有以下特征:
脖子没有天鹅的长
脖子较短
翅膀是黑色的
是个小短腿
飞行的时候,会排成一字!
我是第 3 只大雁,我属于雁类!
我有以下特征:
脖子没有天鹅的长
脖子不长不短
翅膀上羽毛很多
是个大长腿
飞行的时候,会排成八字!
第 1 只大雁的 myNeck 属性: 我脖子的长度超过了天鹅的脖子
第 2 只大雁的 myNeck 属性: 脖子没有天鹅的长
第 2 只大雁的喙: 喙的基部较高,长度和头部的长度几乎相等
在 Python 中没有对属性和方法的访问权限进行限制。为了保证类内部的某些属性或方法不被外部访问,可以在属性或方法名前面添加单下划线(_foo)、双下划线(__foo)或首位加双下划线(__foo__),从而限制其权限,作用如下表所示:
类型 | 功能 |
---|---|
_foo | 以单下划线开头表示 protested(保护)类型的成员,只允许类本身和子类进行访问,但不能使用 “from module import *” 语句导入 |
__foo | 双下划线表示 private(私有)类型的成员,只允许定义该方法的类本身进行访问,而且不能通过类的实例进行访问,但是可以通过 “类的实例名._类名__xxx” 方式访问 |
__foo__ | 首尾双下划线表示定义特殊方法,一般是系统定义名字,如 __init__() |
示例:
class Swan:
"天鹅类"
_neck = "天鹅的脖子很长" # 定义保护属性
__feather = "天鹅的羽毛很美" # 定义私有属性
def __init__(self):
print("__init__(): ", Swan._neck) # 在实例方法中访问保护属性
print("__init__(): ", Swan.__feather) # 在实例方法中访问私有属性
swan = Swan() # 创建实例对象
print("直接访问保护属性: ", swan._neck) # 保护属性可以通过实例名访问
print("加入类名来访问私有属性: ", swan._Swan__feather) # 私有属性,可以通过 "实例名._类名__xxx" 方式访问
print("直接访问私有属性: ", swan.__feather) # 私有属性不能通过实例名访问,出错
上述例子的运行结果为:
__init__(): 天鹅的脖子很长
__init__(): 天鹅的羽毛很美
直接访问保护属性: 天鹅的脖子很长
加入类名来访问私有属性: 天鹅的羽毛很美
Error
在 Python 中,可以通过 @property(装饰器)将一个方法转换为属性,从而实现用于计算的属性,将方法转换为属性后,可以直接通过方法名来访问方法,而不需要再添加一对小括号 “()”,这样使得代码更简洁。
示例:
class Rect:
def __init__(self, width, height):
self.width = width # 矩形的宽
self.height = height # 矩形的高
@property # 将方法转换为属性
def area(self): # 计算矩形的面积的方法
return self.width * self.height # 返回矩形的面积
rect = Rect(800, 600) # 创建实例对象
print("矩形面积为: ", rect.area) # 输出属性的值
上述例子的运行结果为:矩形面积为: 480000
默认情况下,创建的类属性或者实例是可以在类体外进行修改的,如果想要限制其不能在类体外修改,可以将其设置为私有的,但设置为私有后,在类体外也不能获取它的值。如果想要创建一个可以读取但不能修改的属性,可以使用 @property 属性。
示例:
class TVshow:
list = ["战狼2", "红海行动", "湄公河行动", "功夫", "魔童降世之哪吒"]
def __init__(self, show):
self.__show = show
@property
def show(self):
return self.__show
@show.setter # 设置 setter 方法,让属性可修改
def show(self, value):
if value in TVshow.list:
self.__show = "您选择了 《" + value + "》,稍后将播放" # 返回修改的值
else:
self.__show = "您点播的电影不存在"
tvshow = TVshow("战狼2")
print("正在播放: 《", tvshow.show, "》")
print("您可以从 ", tvshow.list, " 中选择要点播的电影")
tvshow.show = "红海行动" # 修改属性值
print(tvshow.show) # 获取属性值
tvshow.show = "捉妖记2" # 修改属性值
print(tvshow.show) # 获取属性值
上述例子的运行结果为:
正在播放: 《 战狼2 》
您可以从 ['战狼2', '红海行动', '湄公河行动', '功夫', '魔童降世之哪吒'] 中选择要点播的电影
您选择了 《红海行动》,稍后将播放
您点播的电影不存在
在编写类时,并不是每次都要从空白开始。当要编写的类和另一个已经存在的类之间存在一定的继承关系时,就可以通过继承来达到代码重用的目的,提高开发效率。
通过继承不仅可以实现代码的重用,还可以通过继承来理顺类与类之间的关系。在 Python 中,可以在类定义语句中,类名右侧使用一对小括号将要继承的基类名称括起来,从而实现类的继承,具体语法格式如下:
class ClassName(baseClassList):
"类的帮助信息" # 类文档字符串
Statement # 类体
ClassName:用于指定类名;
baseClassList:用于指定要继承的基类,可以用多个,类名之间用逗号 ‘,’ 分隔。如果不指定,将使用所有 Python 对象的根类 object;
“类的帮助信息”:用于指定类的文档字符串,定义该字符串后,在创建类的对象时,输入类名和左侧的括号 ‘(’ 后,将显示该信息;
Statement:类体,主要由类变量(或类成员)、方法和属性等定义语句组成。如果在定义类时,没想好类的具体功能,也可以在类体中直接使用 pass 语句代替;
示例:
class Fruit: # 定义水果类(基类)
color = "绿色" # 定义类属性
def harvest(self, color):
print("水果是: " + color + "的") # 输出的是形式参数 color
print("水果已经收获......")
print("水果原来是: " + Fruit.color + "的\n") # 输出的是类属性 color
class Apple(Fruit): # 定义苹果类(派生类)
color = "红色"
def __init__(self):
print("我是苹果")
class Orange(Fruit): # 定义橘子类(派生类)
color = "橙色"
def __init__(self):
print("我是橙子")
def harvest(self, color): # 重写基类中的方法
print("橙子是: " + color + "的")
print("橙子已经收获......")
print("橙子原来是: " + Fruit.color + "的")
apple = Apple() # 创建实例对象(苹果)
apple.harvest(apple.color) # 调用基类的 harvest() 方法
orange = Orange() # 创建实例对象(橘子)
orange.harvest(orange.color) # 调用派生类的 harvest() 方法
上述例子的运行结果为:
我是苹果
水果是: 红色的
水果已经收获......
水果原来是: 绿色的
我是橙子
橙子是: 橙色的
橙子已经收获......
橙子原来是: 绿色的
在派生类中定义 __init__() 方法时,不会自动调用基类的 __init__() 方法。如果要让派生类调用基类的 __init__() 方法进行必要的初始化时,需要在派生类中使用 super() 函数调用基类的 __init__() 方法。
示例:
class Fruit: # 定义水果类(基类)
def __init__(self, color = "绿色"):
Fruit.color = color # 定义类属性
def harvest(self, color):
print("水果是: " + color + "的") # 输出的是形式参数 color
print("水果已经收获......")
print("水果原来是: " + Fruit.color + "的\n") # 输出的是类属性 color
class Apple(Fruit): # 定义苹果类(派生类)
color = "红色"
def __init__(self):
print("我是苹果")
super().__init__() # 调用基类的 __init__() 方法
class Sapodilla(Fruit): # 定义人参果类(派生类)
def __init__(self, color):
print("我是人参果")
super().__init__(color) # 调用基类的 __init__() 方法
def harvest(self, color): # 重写基类中的方法
print("人参果是:" + color + "的")
print("人参果已经收获......")
print("人参果原来是: " + Fruit.color + "的")
apple = Apple() # 创建实例对象(苹果)
apple.harvest(apple.color) # 调用基类的 harvest() 方法
sapodilla = Sapodilla("白色") # 创建实例对象(人参果)
sapodilla.harvest("金黄色带紫色条纹") # 调用派生类的 harvest() 方法
上述例子的运行结果为:
我是苹果
水果是: 红色的
水果已经收获......
水果原来是: 绿色的
我是人参果
人参果是:金黄色带紫色条纹的
人参果已经收获......
人参果原来是: 白色的