来源:《Python从小白到大牛》关东升
面向对象是Python最重要的特性,在Python中一切数据类型都是面向对象的
1、面向对象概述
面向对象的编程思想是,按照真实世界客观事物的自然规律进行分析,客观世界中存在什么样的实体,构建的软件系统就存在什么样的实体。
例如,在真实世界的学校里,会有学生和老师等实体,学生有学号、姓名、所在班级等属性(数据),学生还有学习、提问、吃饭和走路等操作。学生只是抽象的描述,这个抽象的描述称为"类".在学校里活动的是学生个体,即张同学、李同学等,这些具体的个体称为"对象",对象也称为"实例"。
在现实世界有类和对象,软件世界也有面向对象,只不过他们以某种计算机语言编程程序代码形式存在,这就是面向对象编程(Object Oriented Programming,OOP)
2、面向对象三个基本特征
面向对象思想有三个基本特征:封装性、继承性和多态性。
2.1封装性
在现实世界中封装的例子到处都是。例如,一台计算机内部极其复杂,有主板、CPU、硬盘和内存,而一般用户不需要了解它的内部细节,不需要知道各个部件的属性。
面向对象的封装与真实世界的目的是一样的。封装能够使外部访问者不能随意存取对象的内部数据,隐藏了对象的内部细节,只保留有限的对外接口。外部访问这不用关心对象的内部细节,操作对象变得简单。
2.2继承性
特殊类拥有一般类的全部数据和操作,称为特殊类继承一般类。一般类称为"父类"或"超类",特殊类称为"子类"或"派生类"。
2.3多态性
多态性是指父类中成员被子类继承之后,可以具有不同的撞他啊或表现行为。
3、类的对象
Python中的数据类型都是类,类是组成Python程序的基本要素,它封装了一类对象的数据和操作。
3.1定义类
Python语言中一个类的实现包括类定义和类体。类定义语法格式如下:
class 类名[(父类)]:
类体
其中,class是声明类的关键字,"类名"是自定义的类名,自定义类名首先应该是合法的标识符,且应该遵守Python命名规范,采用大驼峰法命名法。"父类"声明当前类继承的父类,父类可以省略声明,表示直接继承object类。
定义动物(Animal)类代码如下:
class Animal(object):
# 类体
pass
上述代码声明了动物类,他继承了object类,object是所用类的根类,在Python中任何一个动物类都直接或间接继承object,所以(object)部分代码可以省略。
注意:代码的pass语句什么操作都不执行,用来维持程序结构的完整。有些不想编写的代码,又不想有语法错误,可以使用pass语句占位。
3.2创建和使用对象
类实例化可生成对象,所以"对象"也称为"实例"。一个对象的生命周期包括三个阶段:创建、使用和销毁。销毁对象时Python的垃圾回收机制释放不再使用对象的内存,不需要程序员负责。程序员只关心创建和使用对象,这一节介绍创建和使用对象。
创建对象很简单,就是在类后面加上一对小括号,表示调用类的构造方法。这就是创建了一个对象,实例代码如下:
animal=Animal()
Animal是上一节定义的动物类,Animal()表达式创建了一个动物对象,并把创建的对象赋值给animal变量,animal是指向动物对象的一个引用。通过animal变量可以使用刚刚创建的动物对象。
print(animal) # 输出结果如下:<_main_.Animal object at 0x0000024A18CB90F0>
print函数打印对象会输出一些很难懂的信息。事实上,print函数调用了对象的_str_()方法输出字符串信息,str()是object类的一个方法,他会返回有关该对象的描述信息,由于本例中Animal类的_str_()方法是默认实现的,所以会返回这些难懂的信息,如果要打印出友好的信息,需要重写_str_()方法。
提示:_str_()这种双下划线开始和结尾的方法是Python保留的,有着特殊的含义,称为魔法方法。
3.3实例变量
在类体中可以包含类的成员,其中包括成员变量、成员方法和属性,成员变量又分为实例变量和类变量,成员方法又分为实例方法、类方法和静态方法。
提示:在Python类成员中有attribute和property。attribute是类中保存数据的变量,如果需要对attribute进行封装,那么在类的外部为了访问这些attribute,往往会提供一些setter和getter访问器。setter访问器是对attribute赋值的方法,getter访问器是取attribute指的方法,这些方法在创建和调用时都比较麻烦,于是Python又提供了property,property本质上就是setter和getter访问器,是一种方法。一般情况下attribute和property中文都翻译为"属性",这样很难区别两者的含义,也有很多书将attribute翻译为"特征"。"属性"和"特征"在中文中区别也不大。其实很多语言都有attribute和property概念,例如Object-C中attribute称为成员变量(或字段),property称为属性。本文采用Object-C提供将attribute翻译为 "成员变量",而property翻译为"属性"。
“实例变量"就是某个实例(或对象)个体特有的"数据”,例如你家狗狗的名字、年龄和性别与邻居家狗狗的名字、年龄和性别是不同的。
Python中定义实例变量的实例代码:
class Animal(object):
"""定义动物类"""
def _init_(self,age,sex,weight):
self.age=age # 定义年龄实例变量
self.sex=sex # 定义性别实例变量
self.weight=weight # 定义体中实例变量
animal=Animal(2,1,10.0)
print('年龄:{0}'.format(animal.age))
print('性别:{0}'.format('雌性' if animal.sex==0 else '雄性'))
print('体重:{0}'.format(animal.weight))
3.4类变量
“类变量”是所有实例(或对象)共有的对象。例如有一个Account(银行账户)类,它有三个成员变量:amount(账户金额)、interest_rate(利率)和owner(账户名)。在这三个成员变量中,amount和owner会因人而异,对于不同的账户这些内容是不同的,而所有账户的interest_rate都是相同的。amount和owner成员变量与账户个体实例有关,称为“实例变量”,interest_rate成员变量与个体实例无关,或者说所有账户实例共享的,这种变量称为“类变量”。
类变量实例代码如下:
class Account:
"""定义银行账户类"""
interest_rate=0.0668 # 类变量利率
def _inti_(self,owner,amount):
self.owner=owner # 定义实例变量账户名
self.amount=amount # 定义实例变量账户金额
account=Account('Tony',1_800_00.0)
print('账户名:{0}'.format(account.owner)) # 账户名:Tony
print('账户金额:{0}'.format(account.amount)) # 账户金额:180000
print('利率:{0}'.format(Account.interest_rate)) # 利率:0.0668
注意:不要通过实例存取类变量数据。当通过实例读取变量时,Python解释器会先在实例中找这个变量,如果没有再到类中去找;当通过实例为变量赋值时,无论类中是否有该同名变量,Python解释器都会创建一个同名实例变量。
print('Account利率:{0}'.format(Account.interest_rate)) # Account利率:0.0668
print('ac1利率:{0}'.format(Account.interest_rate)) # ac1利率:0.0668
通过实例读取interest_rate变量,解释器发现account实例中没有该变量,然后会在Account类中找,如果类中也没有,会发生AttributeError错误。
虽然通过实例读取interest_rate变量可以实现,但不符合涉及规范。
print('ac1实例所有元素:{0}'.format(account._dict_)) # ac1实例所有元素:{'owner':'Tony','amount':'1800000.0'}
account.interest_rate=0.01
account.interest_rate2=0.01
print('ac1实例所有元素:{0}'.format(account._dict_)) # ac1实例所有元素:{'owner':'Tony','amount':'1800000.0',
'interest_rate':0.01,'interest_rate2':0.01}
account.interest_rate为变量赋值,这样的操作下无论类中是否有同名类变量都会创建一个新的实例变量。为了查看实例变量有哪些,而可以通过object提供 _dict_变量查看。
提示:能够在类之外创建实例变量,主要原因是Python的动态语言特性,Python不能从语法层面禁止此事的发生。这样创建实例变量会引起很严重的问题,
一方面类的设计者无法控制一个类中有哪些成员变量;另一方面,这些实例变量无法通过类中的无法访问。
3.5构造方法
上述使用了_init_()方法,该方法用来创建和初始化实例变量,这种方法就是“构造方法”。init()方法也属于魔法方法。定义时它的第一个参数应该是self,其后的参数才能用来初始化实例变量的。调用构造方法时不需要传入self。
3.6实例方法
实例方法与实例变量一样都是某个实例(或对象)个体特有的。
方法是在类中定义的函数。而定义实例方法时它的第一个参数也应该是self,这个过程是将当前实例与该方法绑定起来,使该方法称为实例方法。
下面看一个定义实例方法示例。
class Animal(object):
"""定义动物类"""
def _init_(self,age,sex=1,weight=10.0):
self.age=age
self.sex=sex
self.weight=weight
def eat(self):
self.weight+=0.05
print('eat...')
def run(self):
self.weight-=0.01
print('run...')
a1=Animal(a,0,10.0)
print('a1体重:{0:0.2f}'.format(a1.weight)) # a1体重:10.0
a1.eat()
print('a1体重:{0:0.2f}'.format(a1.weight)) # a1体重:10.05
a1.run()
print('a1体重:{0:0.2f}'.format(a1.weight)) # a1体重:10.04
上述代码声明了两个方法分别为eat()和run(),其中第一个参数是self.注意,在方法调用时不需要传入self.
3.7类方法
“类方法”与“类变量”类似属于类,不属于个体实例的方法,类方法不需要与实例绑定,但需要与类绑定,定义时它的第一个参数不是self,而是类的type实例。type是描述Python数据类型的类,Python中所有数据类型都是type的一个实例。
定义类方法示例代码如下:
class Account:
"""定义银行账户类"""
interest_rate=0.0668 # 类变量利率
def _inti_(self,owner,amount):
self.owner=owner # 定义实例变量账户名
self.amount=amount # 定义实例变量账户金额
# 类方法
@classmethod
def interest_by(cls,amt):
return cls.interest_rate * amt
interest = Account.interest_by(12_000.0)
print('计算利息:{0:4f}'.format(interest)) # 计算利息:801.6000
定义类方法有两个关键:第一,方法第一个参数cls是type类型,是当前Account类型的实例;第二,方法使用装饰器@classmethod声明该方法是类方法。
提示:装饰器(Decorators)是Python3.0之后加入的新特征,以@开头修饰函数、方法和类,用来修饰和约束它们,类似于Java中的注释。
类方法定义中的返回值是方法体,在类方法中可以访问其他的类变量和类方法,cls.interest_rate是访问类方法interest_rate.
注意:类方法可以访问类变量和其他类方法,但不能访问其他实例方法和实例变量。
调用类方法interest_by(),采用“类名.类方法”形式调用。从语法角度可以通过实例调用类方法,但这不符合规范。
3.8静态方法
如果定义的方法既不想与实例绑定,也不想与类绑定,只是想把类作为它的命名空间,那么可以定义静态方法。
定义静态方法实例代码如下:
class Account:
"""定义银行账户类"""
interest_rate=0.0668 # 类变量利率
def _inti_(self,owner,amount):
self.owner=owner # 定义实例变量账户名
self.amount=amount # 定义实例变量账户金额
# 类方法
@classmethod
def interest_by(cls,amt):
return cls.interest_rate * amt
# 静态方法
@staticmethod
def interest_with(amt):
return Account.interest_by(amt)
interest1 = Account.interest_by(12_000.0)
print('计算利息:{0:4f}'.format(interest1)) # 计算利息:801.6000
interest2 = Account.interest_with(12_000.0)
print('计算利息:{0:4f}'.format(interest2)) # 计算利息:801.6000
上述代码定义了静态方法,使用了@staticmethod装饰器,声明方法是静态方法,方法参数不指定self和cls。返回值中调用了类方法。调用静态方法与调用类方法类似都通过类名实现,但也可以通过实例调用。
类方法与静态方法在很多场景是类似的,只是在定义时有一些区别。类方法需要绑定类,静态方法不需要绑定类,静态方法与类的耦合度更加松散。在一个类中定义静态方法只是为了提供一个基于类名的命名空间。