Python对象
我们希望尽量将函数和函数所要处理的数据相关联,就跟Python内置的数据结构一样,能有效地组织和操作数据。Python中的类就是这样的结构,它是对客观事物的抽象,由数据(即属性)和函数(即方法)组成。
面向对象的特点
若要模拟真实世界,则必须把真实世界的东西抽象化为计算机系统的数据。在面向对象的世界中,是以各个对象自行分担的功能来产生模块化的,它基本上包含三个基本元素:数据抽象化(封装)、继承和多态(动态绑定)
封装
封装(Encapsulation)就是利用“类”来实现“抽象数据类型”(ADT)。类是一种用来具体描述对象状态与行为的数据类型,也可以看成是一个模型或蓝图,按照这个模型或蓝图所产生的实例(Instance)就被称为对象。
所谓“抽象”,就是将代表事物特征的数据隐藏起来,并定义一些方法作为操作这些数据的接口,让用户只能接触到这些方法,而无法直接使用数据,也符合信息隐藏的要求,而这种自定义的数据类型就称为“抽象数据类型”。
继承
继承(Inheritance)是面向对象程序设计语言强大的功能,因为它允许程序代码的重复使用(Code Reusability,即代码可重复使用性),同时可以表达树形结构中父代与子代的遗传现象。“继承”类似现实生活中的遗传,允许我们定义一个新的类来继承现有的类,进而使用或修改继承而来的方法,并可在子类中加入新的数据成员与函数成员。
多态
多态(Polymorphism)也是面向对象设计的重要特性,可让软件在开发和维护时达到充分的延伸性。多态,按照英文单词字面的解释,就是一样东西同时具有多种不同的类型。在面向对象程序设计语言中,多态的定义简单来说就是利用类的继承结构先建立一个基类对象。用户可通过对象的继承声明将此对象向下继承为派生类对象,进而控制所有派生类的“同名异式”成员方法。简单地说,多态最直接的定义就是让具有继承关系的不同类的对象可以调用相同名称的成员函数,并产生不同的反应结果。
面向对象程序设计中的关键术语
对象
对象(Object)可以是抽象的概念或一个具体的东西,包括“数据”(Data)及其所相应的“操作”或“运算”(Operation),或称为方法(Method),它具有状态(State)、行为(Behavior)与标识(Identity)。
每一个对象均有其相应的属性(Attribute)及属性值(Attribute Value)。例如,有一个对象称为学生,“开学”是一条信息,可传送给这个对象。而学生有学号、姓名、出生年月日、住址、电话等属性,当前的属性值便是其状态。学生对象的操作或运算行为则有注册、选修、转系、毕业等,学号是学生对象的唯一识别编号(对象标识,OID)。
类
类(Class)是具有相同结构及行为的对象集合,是许多对象共同特征的描述或对象的抽象化。例如,小明与小华都属于人这个类,他们都有出生年月日、血型、身高、体重等等类的属性。类中的一个对象有时就称为该类的一个实例(Instance)。
属性
属性(Attribute)用来描述对象的基本特征及其所属的性质,例如一个人的属性可能包括姓名、住址、年龄、出生年月日等。
方法
方法(Method)是面向对象数据库系统中对象的动作与行为,我们在此以人为例,不同的职业,其工作内容也会有所不同,例如:学生的主要工作为学习,而老师的主要工作为教书。
有了这些面向对象的基本概念,接下来将以Python语言配合面向对象程序设计的概念,深入探讨类和对象的实现。
Python语言的类机制主要包括以下特性:
- 默认所有的类与其包含的成员都是公有的,可以不用public关键字来声明该类的类型。
- 采用多重继承,派生类和基类的方法可以有相同的名称,也能覆盖其所有基类的任何方法。
Python对象概述
类提供了实现对象的模型,类就如同盖房屋之前的规划蓝图,也可以把类看成是一种“数据类型”,但没有实例。编写程序时,必须先定义类,并定义成员的属性和方法。类只有实例化为对象后,才可以使用。也就是说,类只是对事物的设计,对象才是成品。
创建类之后,还要具体化对象,这个过程称为“实例化”,经过实例化的对象称为“实例”(Instance,或实体)。也就是说,创建对象之前,必须定义类,如此一来,才可以通过该类实现对象的实例。
定义类
类是面向对象编程思想的载体。Python的面向对象编程提供了丰富的封装手段,类定义的规则非常灵活,既有强制性的,也有建议性的。 类是由类成员(Class Member)组成的,类在使用之前要进行声明,语法如下:
class 类名称():
# 定义初始化内容
# 定义方法
- class:创建类的关键字,但必须配合冒号“:”产生程序区块。
- 类名称:用来指定所要创建类的名称,类名称同样必须遵守标识符的命名规范。
- 定义方法时,与先前介绍过的自定义函数一样,必须使用def语句。
如果不想深入研究那些令人头疼的概念,只需要了解以下这几点就可以从容应对类的各种需求。
- 使用关键字class定义类。
- 如果没有基类,类名之后不需要圆括号。
- 构造函数__init__( )在类实例化时自动运行,类的属性要在这里定义或声明。
- self不是关键词,虽然可以换成其他的写法,但不建议这样做。
- 类是属性和方法的混合体。
- 同一个类,可以生成很多实例(单实例模式除外),这叫类的实例化。
- 类的各个实例之间是相互隔离的。
以下语句是最简单的定义类的方式,用来创建一个空类:
class nothing:
pass
上面的语句用来创建nothing类,使用pass语句表示什么事都不做。 在定义类的过程中可以加入属性和方法,再以对象来存取其属性和方法。
以下为描述人这个类的代码示例:
class People: #定义人的类
#构造函数,生成类的对象时自动调用
def __init__(self,my_name,my_age,my_sex): # 构造函数
self.name = my_name #类属性:姓名
self.age = my_age #类属性:年龄
self.sex = my_sex #类属性:性别
#定义类方法:获取姓名
def get_name(self):
return self.name
#定义类方法:打印信息
def get_information(self):
print("name:%s,age:%d,sex:%s"%(self.name,self.age,self.sex))
- 使用class关键字定义一个类,其后接类名People,类名后面接冒号(:)。
__init__()
方法是一种特殊的方法,被称为类的构造函数或初始化方法,当创建了这个类的实例时就会调用该方法。注意,init两边分别有两个下划线。- self代表类的实例。在定义类的方法时,self要作为参数传递进来,虽然在调用时不必传入相应的参数。
- 类的属性有:name、age和sex。使用属性时要在前面要加上self。
- 类的方法有:get_name(self)和get_information(self)。注意,这里要有参数self。
- 类的方法与普通的函数只有一个区别,它们必须有一个额外的第一个参数名称,按照惯例它是self。
类实例化
将类实例化就是创建对象,有了对象才可以进一步存取类中所定义的属性和方法。类实例化的语法如下:
对象 = 类名称(参数列表)
对象.属性
对象.方法()
其中的对象名称同样要遵守标识符的规范。参数列表可根据对象初始化进行选择。下面的程序语句可生成或创建两个对象:boy1和boy2。
boy1 = Person()
boy2 = Person()
上面的程序语句可视为“创建Person类的实例,并将该对象赋值给变量boy1和boy2”。
重申一遍,类只有实例化为对象后才可以使用。例如要生成同学cathy的对象,实现代码如下:
#主函数
if __name__ == "__main__":
#生成类的对象,赋初值
cathy = People("cathy",10,"女")
print(cathy.get_name()) #调用方法并打印,得到:"cathy"
cathy.get_information() #调用方法,得到:name:cathy,age:10,sex:女
- 使用类名People,生成该类的对象cathy,并传入参数cathy,10和“女”。
- 实例化为对象cathy时,自动调用__init__()构造函数,并接收传入的参数。
- 使用点号(.)来访问对象的属性和方法,如cathy.get_name()。
Python定义方法的第一个参数必须是自己,习惯上使用self来表示,它代表创建类后实例化的对象。这是一个相当重要的特性,和其他程序设计语言有所不同。self有点类似其他语言中的this,指向对象本身,在定义类的所有方法中都必须声明它。
下面以一个简单的范例程序来说明在Python语言中定义类的用法。
class Person:
#定义方法一:获取姓名和年龄
def setData(self, name, age):
self.name = name
self.age = age
#定义方法二:输出姓名和年龄
def showData(self):
print('姓名:{0:6s}, 年龄:{1:4s}'.format(
self.name, self.age))
# 创建对象
boy1=Person()#对象1
boy1.setData('John', '16')
boy1.showData() #调用方法
boy2=Person()#对象2
boy2.setData('Andy', '14')
boy2.showData()
此外,再次强调一下这个范例中使用的特别关键字self(此处我们以self语句来称呼它)。通常name和age只是变量,它们定义于方法内,属于局部变量,离开作用域,它们的“生命周期”就结束了。self不进行任何参数的传递,通过self语句的加入,它们成了对象变量,于是可以让方法之外的对象来存取。
def setData(self, name, age):
self.name = name
self.age = age
所以将参数name的值传给self.name会让一个普通的变量转变成对象变量(也就是属性),并可由对象来存取。定义类之后,还可以根据需求传入不同类型的数据。
下面通过一个简单的范例程序来说明。
class Money:
def setValue(self,amount): #第一种方法
self.amount =amount
def showValue(self): #第二种方法
print("钱的总数为:",self.amount)
s1 = Money()#第一个对象
s1.setValue("叁万捌仟元整")#调用方法时传入字符串
s1.showValue()
s2 = Money()#第二个对象
s2.setValue(34500)#调用方法时传入浮点数
s2.showValue()
将对象初始化的__init__()方法
前面的几节介绍了对象的属性与方法,接下来介绍可以初始化对象的__init__()方法。当创建对象时,这个特殊方法会初始化对象,例如设置初值或进行数据库连接等。这个方法的第一个参数是self,指向刚创建的对象本身。创建对象之后,良好的习惯是调用__init__()方法设置初值。
此处以计算Rectangle类为例,通常先创建Rectangle对象,并设置其长与宽的初值,再于程序中将长与宽设置成要计算的值。虽然这种方式也可以将长与宽设置成要计算的值,但比较好的做法是在创建对象时,就通过__init__()方法为该对象设置好长与宽的初值。以下范例程序将为大家说明如何通过__init__()方法为对象设置初值:
class Rectangle:
# 定义一个init()方法,并设置默认值。但是如果有参数传入,还是可以通过__init__()方法设置实际的长与宽,最后计算出实际参数对应的面积。
def __init__(self, length=10, width=5):
self.length=length
self.width=width
def getArea(self):
return self.length* self.width
R1=Rectangle()
print("通过init()方法初始化设置的默认面积: ",R1.getArea())
R2= Rectangle(125,6)
print("通过传入参数值计算的面积: ",R2.getArea())
匿名对象
通常声明类后,会将类实例化为对象,并将对象赋值给变量,再通过这个变量来存取对象。在Python中有一项重要特性,就是每个东西都是对象,所以在编写程序时,也可以在不把对象赋值给变量的情况下使用对象,这就是一种被称为匿名对象(Anonymous Object)的程序设计技巧。
直接以匿名对象的方式存取对象,不用像上一个例子那样,还要先将对象赋值给一个变量,再通过变量来存取该对象。
class Rectangle:
def __init__(self, length=10, width=5):
self.length=length
self.width=width
def getArea(self):
return self.length* self.width
print("通过init()方法初始化设置的默认面积: ",Rectangle().getArea())
print("通过传入参数值计算的面积: ",Rectangle(125,6).getArea())
私有属性与方法
在前面的范例程序中,类外部的指令可以直接存取类内部的数据,这种做法具有风险,容易造成内部数据被不当修改。如果某些类内部的重要数据不希望被任何人擅自修改,目前这种编写程序的方式就存在问题。
为了达到保护类内部数据的目的,我们可以将这些重要数据设置为私有属性,如此一来,就可以限定该数据只能由类内部的指令或语句来存取。当类外部想要存取这些私有的属性数据时,就不能够直接从类外部进行存取,必须通过该类所提供的公有方法。这种做法较为安全,可以有效保护一些不想被修改的重要数据。
想要把属性指定为私有的,只要在该属性名称前面加上两个下画线“__”即可,就代表该属性为私有属性。不过要特别注意,在名称后面不能有下画线,例如__age是私有属性,但是__age__就不是私有属性。
下面的范例程序为私有属性的应用例子,其中__rate是私有属性,当类外部想要存取__rate私有属性的数据时,并不能够直接从类外部进行存取,必须通过该类所提供的getRate()公有方法。
class Discount:
def __init__(self, r=0.9):
self.__rate=r
def getRate(self):
return self.__rate
def howMuch(self):
return money*self.__rate
money=10000
obj1=Discount(0.7)
print("此件商品的原有价格为",money,"元")
print("这件商品的折扣为", obj1.getRate())
print("打折扣后商品的价格为", obj1.howMuch(),"元")
从上面的执行结果中可以看到,由于__rate是一个私有属性,因此类外部的指令无法直接存取该私有属性,必须通过类中定义的getRate()方法才能取得实际的年龄。
除了属性成员可以设置为“私有的”之外,类的方法成员如果要设置为“私有的”,在程序中设置的方式和指定私有属性类似,只要在方法名称前加上两个下画线“__”即可,而且名称之后不能再有下画线。一旦被声明为私有方法后,该方法只能被类内部的语句调用,在类外部就不能直接调用该私有方法了。其实无论是私有属性还是私有方法,其目的就是帮助程序设计人员为需要保护的属性与方法加上一道保护措施,以达到信息隐藏的目的。
继承
继承除了可重复使用已开发的类之外,另一项好处在于维持对象封装的特性。这是因为继承时不容易改变已经设计好的类,于是降低了类设计发生错误的机会。
在Python中,已创建好的类被称为“基类”(Base Class),而经由继承所产生的新类被称为“派生类”(Derived Class)。通常会将基类称为父类(Parent Class)或超类(Super Class),而将派生类称为子类(Child Class)。类之间如果要有继承(Inheritance)的关系,就必须先创建好基类。
从另一个思考角来看,我们可以把继承单纯地视为一种复制(Copy)操作。换句话说,当开发人员以继承机制声明新类时,会先将所参照的原始类中的所有成员完整地写入新建类之中。
单一继承与多重继承
单一继承(Single Inheritance)是指派生类只继承单独一个基类。在Python中,使用继承机制定义子类的语法格式如下:
class ChildClass(ParentClass):
程序语句
或
class ChildClass(ParentClass1, ParentClass2,…):
程序语句
第一种语法子类只继承单一父类,这是单一继承的语法格式;第二种语法子类继承一个以上的父类,这是一种多种继承的语法格式。
下面我们先来看单一继承的实例,这个范例程序会先定义Vehicle基类,接着以继承的语法格式定义Bike派生类,程序代码如下。
#类的继承
class Vehicle: #基类
def move(self):
print('我是可以移动的交通工具')
class Bike(Vehicle): #派生类
pass
#创建子类的实例
giant = Bike()
giant.move()
其实子类还可以扩展父类的方法而不是完全取代它,我们可以将上面的范例程序修改如下,除了调用父类的方法外,还根据自己的需求扩展了子类的方法。
#类的继承
class Vehicle: #基类
def move(self):
print('我是可以移动的交通工具')
class Bike(Vehicle): #派生类
def move(self):
Vehicle.move(self)
print('但我必须以脚踏的方式来移动')
#创建子类的实例
giant = Bike()
giant.move()
另外,Python支持链式继承,例如在下面的范例程序中,类Father继承类GrandFather,而类Son继承类Father。
#类的继承
class Vehicle: #基类
def move(self):
print('我是可以移动的交通工具')
class Bike(Vehicle): #派生类
def move(self):
Vehicle.move(self)
print('但我必须以脚踏的方式来移动')
class Child_Bike(Bike): #孙类
def move(self):
Bike.move(self)
print('为了小朋友的安全,我特别加上辅助轮')
#创建孙类的实例
small_giant = Child_Bike()
small_giant.move()
不仅如此,一个子类可以同时继承多个父类,我们以一个简单的范例程序来示范Python的多重继承。
#类的继承
class Animal: #基类一
def feature1(self):
print('动物是多细胞真核生命体中的一大类群')
class Human: #基类二
def feature2(self):
print('人类是一种具有情感的高等智能动物')
class Boy(Animal, Human): #派生类
pass
#创建子类的实例
Andy = Boy()
Andy.feature1()
Andy.feature2()
覆盖
覆盖(Override)是重新定义从父类中继承而来的方法,虽然在子类中重新改写从父类继承过来的方法,但并不会影响父类中对应的这个方法。
先以一个范例程序说明如何在子类覆盖父类的方法。
#子类覆盖父类的方法
class Discount(): #父类
def rate(self, total):
self.price = total
if self.price >= 20000:
print('平时假日的折扣为9折:', end = ' ')
return total * 0.9
class Festival(Discount): #子类
def rate(self, total): #覆盖rate方法
self.price = total
if self.price >= 50000:
print('节庆特优惠的折扣为5折:', end = ' ')
return total * 0.5
Jane = Discount()#创建父类对象
print(Jane.rate(78000))
Mary = Festival()#创建子类对象
print(Mary.rate(78000))