一、面向对象的介绍
计算机编程中最常被提到的就是类和对象,掌握类和对象,有助于使用Python编程语言快速实现复杂的项目。
早期计算机编程语言都是面向过程的,程序即由数据和算法构成,数据可以构成复杂的数据结构,算法也是由上到下的复杂逻辑控制,这是一个将数据与操作算法分离的编程思想。这种程序设计思想的重点都是在代码中各个方法的执行上。C语言中提供了结构体来解决数据复杂度问题,可将一部分数据或属性包装起来,定义出一个复杂的数据结构,如Person结构体(包括姓名、年龄、身高、体重等一系列数据)。
# include <stdio.h>
int main(){
//定义结构体Person
struct Person{
//结构体包含的变量
char *name;
int age;
float height;
float weight;
};
//通过结构体来定义变量
struct Person person;
//操作结构体的成员
person.name = "小王";
person.age = 25;
person.height = 181.2;
person.weight = 75.0;
return 0;
}
上面的代码是C语言设计中的结构体,但是这种结构只能支持复杂的数据结构,对于每个数据的处理都要单独提供方法,而且这些方法与整体的结构体并没有关系。后来在PHP、C++、Java、Python等语言中,人们对C语言中的结构体进行了升级,引入了一种新的编程思想——面向对象编程,即程序操作都是在操作对象。对象不仅可以定义复杂的数据结构,也可以定义复杂的算法方程。对象将数据和方法封装起来,开发者只需要负责对象内部的数据和算法,同时对外暴露接口供调用方使用。而调用方也不需要关心实际对象内部的复杂逻辑,只需要调用接口。下面给出了和PHP相同的Person结构示例,与C语言结构体不同的是加入了算法和方法。
class Person:
def __int__(self,name,age,height,weight):
self.name = name
self.age = age
self.height = height
self.weight = weight
def print_person(self):
print("姓名",self.name)
print("年龄",self.age)
print("年龄",self.height)
print("年龄",self.weight)
person = Person('零壹快学',1,1.8,70)
person.print_person()
开发人员发现,这种编程思想更能解决实际问题,程序是对象的集合,并且能够描述具有高度复杂性的各类模型和算法。在现实生活中,人的思考是抽象的,我们会将遇到的事物抽象化,相同或类似的对象可以进一步进行抽象,这时就出现了对象类型——类。先定义类,然后
由类去创建对象,最后由对象去管理整个程序,就像人类思考一样,先抽象,后实例化,最后去执行,面向对象编程的应用逐渐变得广泛起来。
值得注意的是,类的产生是抽象的结果,人们在认识复杂世界时,会将实物或者可以说是对象的一些近似特征抽出来,并且不考虑其中每个个体的细节。面向对象编程就是一种不断抽象数据和抽象方法的过程。
提示
面向对象编程在软件执行效率上并没有绝对优势,主要是一种方便开发者组织管理代码、快速梳理熟悉各个业务领域逻辑的思想方法,它是一种更为贴近现实生活的编程设计方法。
1、对象
万物皆对象。现实世界中我们所能见到的、能触摸到的所有人和事物都是对象,如人、猫、狗、汽车等。在计算机的世界里面,我们用虚拟的编程代码将现实世界的事物抽象成对象,然后用面向对象编程思想来解决现实世界中的种种难题。对象可以是有形的,也可以是无形的。人们在认识世界的时候,会将对象简单处理分为两个部分——属性和行为。
对象具有属性,它可以称为变量。正如每个人都有姓名、年龄、身高、体重等属性,我们可以用这些数据来描述对象的属性,如下图所示:
同一类的对象虽然都具有相同的属性,但是其中每个对象是不同的,这表现为每个对象各自的属性值并不同。如下所示:
对象具有行为,也可以称为方法,就如同每个人都要吃饭、睡觉、运动一样,如下图所示。面向对象编程将完成某个功能代码块定义为方法,方法可以被其他程序调用,也可以被对象自身调用。举个简单的例子,成人可以自己去吃饭,宝宝可以被成人喂饭。
同样,每个对象的行为也是不相同的,如图所示:
从上面几个示例中我们可以看到,实际的“小王”和“小刘”对象,都具有“人”属性和行为,这里的“人”就是类。
2、类
前面提到,类是相同类似对象的统称。人就是一种类,每个人——即这类对象,都有姓名、年龄、身高、体重等属性,每个人也都有吃饭、睡觉、运动等行为。类是对象的抽象,对象则是类的实例化、具体化,每个对象都包括了类中定义的属性和行为,如下图所示:
类是对象的属性和行为被进一步封装的模板,不同的类之间的属性和行为都是不同的,如图所示:
编程语言中,类的属性是以成员属性(也可称成员变量)来定义的,类的行为是以成员方法来定义的,后续章节小编会向大家逐渐介绍。
二、Python与面向对象
Python从设计之初就已经是一门面向对象的语言了,正因为如此,在Python中创建一个类和对象很容易的。本节我们将详细介绍Python面向对象编程。
1、介绍
接下来我们先来简单地了解一些面向对象的基本特征。
- 类(Class):用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
- 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外,通常不作为实例变量使用。
- 数据成员:类变量或者实例变量,用于处理类及其实例对象的相关数据。
- 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程就叫做方法的覆盖(Override),也称为方法的重写。
- 实例变量:定义在方法中的变量,只作用于当前实例的类。
- 继承:指一个派生类(Derived Class)继承基类(Bass Class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是一个模拟“是一个(is-a)”关系。
- 实例化:创建一个类的实例,一个类的具体对象。
- 方法:类中定义的函数。
- 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法
2、定义语法
类必须在被定义后才能使用,定义一个类也就是定义这一类对象的模板,定义它的属性和方法。Python中提供了class关键字来声明一个类,class中有成员属性和成员方法。Python中类的定义格式如下:
class [类名]:
[语法块]
注意:类名和变量名一样区分大小写。字母相同但是大小写不同的类会被解释器视为两个不同的类。
定义一个类:
class EmptyClass:
pass
在这个例子中我们定义了一个空类,虽然什么都没有做,但是不影响它的存在。
3、类的使用
在使用之前需要实例化类,类实例化后会成为常见对象。创建对象和创建变量类似,需要先声明对象是哪个类,同时指明变量名称。
class EmptyClass:
pass
empty = EmptyClass()
print(type(empty))
执行结果如下:
<class '__main__.EmptyClass'>
这个例子创建一个EmptyClass对象,变量名为“empty” ,同时在内存中为这个对象分配了内存空间。
4、类的构造方法
在创建实例时,很多类可能都需要有特定的初始状态。所以,一个类可以定义一个特殊的方法,叫做构造函数。在Python中,构造函数就是类__init__方法(init前后都有两个连续的短下划线)。
定义和使用构造函数:
class Dog:
def __init__(self):
print("汪汪汪!!!")
dog = Dog()
执行结果如下:
汪汪汪!!!
当一个类定义了__init__方法,类在实例化时会自动调用__init__方法,用于创建新的类实例。在这个例子中,新的实例被创建,同时执行了构造方法,运行了print()函数。注意:构造函数的第一个参数是“self”,不能漏掉。
还有一点需要注意的是,构造方法的返回值必须是“None”。在定义构造方法的时候解释器不会报错,但是在实例化的时候Python会输出错误提示:“TypeError:__init__() should return None”。
5、类的属性
在构造方法中我们可以初始化一些属性,例如:
class Dog:
def __init__(self,name):
self.name - name
selg.age = 3
dog = Dog("旺财")
print(dog.name)
print(dog.age)
执行结果如下:
旺财
3
注意:属性(或者叫成员变量、类变量)必须要使用“self”加上点的方式赋值,不能直接定义变量。直接定义的变量的生命周期只会在函数内,函数执行完变量就会被销毁。例如:
class Dog:
def __init__(self,name):
self.name = name
age = 3
dog = Dog("旺财")
print(dog.name)
print(dog.age)
执行这个例子,Python解释器将会提示我们“ AttributeError:'Dog' object has no attribute 'age' ”,即“Dog”对象没有“age”这个属性。其实函数__init__第一个参数“self”指代的就是实例本身,在C++等语言中对应的就是“this”指针,可以理解为对实例的属性进行赋值。Python在调用__init__函数的时候会自动地添加实例作为函数的第一个参数。
6.类中的方法
在类中定义函数我们称为方法。其实在前面的章节中我们已经多次调用过方法了,例如dict字典中的keys方法就是成员方法。自己定义成员方法也很简单,例如:
class Dog:
def __init__(self,name):
self.name = name
def play(self):
print("汪汪汪!我是",self.name)
dog = Dog("旺财")
dog.play()
执行结果如下:
汪汪汪!我是旺财
7.私有属性
从前面的例子可以看到,在构造函数中定义了属性,实例可以轻松地获取和修改属性的值。但是有时候我们需要限制实例随意修改属性,这时候就要用到私有属性。定义私有属性很简单。只要在定义属性名字的时候使用两条下划线作为开头,Python解释器就认为这个属性是私有的,外部不能随便访问这个属性。例如:
class Dog:
def __init__(self,name):
self.__name = name
def paly(self):
print("汪汪汪!我是",self.__name)
dog = Dog("旺财")
dog.paly()
# 错误
print(dog.__name)
执行这段代码,Python解释器会出现一段错误提示“ AttributeError: 'Dog' object has no attribute '__name' ”。虽然我们在构造方法中给“__name”赋值了,但是在实例中并不能直接访问到“__name”这个以两个下划线开头的成员变量。在平时的实际项目中,我们可以使用这个特性保护一些不想让用户随便修改的属性。例如:
class Dog:
def __init__(self,name):
self.__name = name
self.__age = None
print(self.__name,"生成成功")
def set_age(self,age):
if not isinstance(age,int):
print("输入年龄必须是数字!")
if age <= 0 :
print("年龄必须大于0!")
return False
self.__age = age
def play(self):
print("汪汪汪!我今年",self.__age)
dog = Dog("旺财")
dog.set__age("hello")
dog.set__age("-20")
dog.set__age(3)
dog.play()
执行结果如下:
旺财生成成功
输入的年龄必须是数字!
年龄必须大于0!
汪汪汪!我今年 3
在这个例子中,“__age”是私有属性,实例化过后只能通过set_age方法设置年龄。在set_age方法中我们限制了“age”只能是“int”并且要大于“0”,有效地限制了实例化后数据内容,保证了“__age”是一个有效可用的数据。
8.私有方法
上面我们讲到私有属性,即只能在类内部被操作的属性。同样地,方法也有私有方法,和私有属性一样,私有方法只能在类内部被调用。实例不直接调用。例如:
class Dog:
def __say(self,name):
print(name)
def play(self):
self.__say("汪汪汪")
dog = Dog()
dog.play()
# 错误
dog.__say()
执行这段代码可以发现play方法可以正常运行,但是__say方法不能直接被实例“dog”调用,Python解释器会提示错误信息“AttributeError: 'Dog' object has no attribute '__say' ”。