Python面向对象
前言
接上篇Python函数及函数式编程,打卡~
简述面向对象编程思想:按照真实客观世界客观事物的自然规律进行分析,客观世界中存在什么样的实例,构建的软件系统就会存在什么样的实体。
注意:类名采用大驼峰命名法(英文单词首字母大写),例如:ClassRroom。
1.1 面向对象的三个基本特征
1.1.1 封装性
封装能够使外部访问者不能随意存取对象的内部数据,隐藏了对象内部的细节,只保留有限的对外接口。外部访问者不用关心对象的内部细节,操作对象变得简单。
1.1.2 继承性
在面向对象中分为一般类和特殊类。特殊类拥有一般类的全部数据和操作,称为特殊类继承一般类。一般类称为“父类”或“超类”,特殊类称为“子类”或“派生类”。
1.1.3 多态性
多态性是指在父类中成员被子类继承之后,可以具有不同的状态或表现行为。
2.1 类和对象
Python中的数据类型都是类,类是组成Python程序的基本元素,它封装了一个类对象的数据和操作。
2.1.1 定义类
Python语言中一个类的定义包括类定义和类体。格式如下:
class Animal(object):
#类体
pass
上述代码声明了动物类,它继承了object类,object是所有类的根类,在Python中任何一个动物类都直接或间接继承object,所以(object)部分代码可以省略。
注意:代码的pass语句什么操作都不执行,用来维持程序结构的完整。有些不想编写的代码,又不想有语法错误,可以使用pass语句占位。
2.1.2 创建和使用对象
“对象”也可称为“实例”。一个对象的生命周期包括三个阶段:创建、使用和销毁。销毁对象时Python的垃圾回收机制释放不再使用的对象的内存,不需要程序员负责。
创建对象的语法很简单,只需在类后面加上一对小括号,表示调用类的构造方法。例如:
animal = Animal()
这样就创建好了,通过animal变量可以使用刚刚创建的动物对象。
2.1.3 实例变量
在类体中可以包含类的成员,类方法如下图所示,其中包括成员变量、成员方法和属性,成员变量又分为实例变量和类变量,成员方法又分为实例方法、类方法和静态方法。
“实例变量”就是某个实例(或对象)个体特有的“数据”,例如你家狗狗的名字、年龄和性别与邻居家的狗狗的名字、年龄和性别是不一样的。
举个例子:
class Animal(object):
'''定义动物类'''
def __init__(self,age,sex,weight):
self.age = age #定义年龄实例变量
self.sex = sex #定义性别实例变量
self.weight = weight #定义体重实例变量
dog1 = Animal(2,1,5)
print('年龄: {0}'.format(dog1.age))
print('性别: {0}'.format(dog1.sex))
print('体重: {0}'.format(dog1.weight))
结果为:
年龄: 2
性别: 1
体重: 5
构造方法是用来创建和初始化实例变量的,有关构造方法下文中会提及。构造方法中的self指向当前对象实例的应用。其中self.age表示对象的age实例变量。访问age实例变量需要通过“实例名.变量名”的形式访问。
2.1.4 类变量
“类变量”是所有实例变量共有的变量。例如有一个Account(银行账户)类,它有三个成员变量 : amount(账户金额)、interest_rate(利率)和owner(账户名)。在这三个成员变量中,amount和owner会因人而异,但是所有账户的interest_rate都是相同的。amount和owner成员变量与账户个体实例有关,称为“实体变量”,interest_rate成员变量与个体实例无关,或者说是个体实例共有的,这个变量被称为“类变量”。
例如:
class Account:
'''定义银行账户类'''
interest_rate = 0.0668
def __init__(self,owner,amount):
self.owner = owner #定义实例变量账户名
self.amount = amount #定义实例变量账户金额
account1 = Account('Tony',18000.0)
account2 = Account('Amy',24000.0)
print('账户{0}的金额为{1},年利率为{2}'.format(account1.owner,account1.amount,account1.interest_rate))
print('账户{0}的金额为{1},年利率为{2}'.format(account2.owner,account2.amount,account2.interest_rate))
运行结果如下:
账户Tony的金额为18000.0,年利率为0.0668
账户Amy的金额为24000.0,年利率为0.0668
可以看到类变量是所有实例变量所共有的。
注意:不要通过实例存取类变量数据。当通过实例读取变量时,Python解释器会在实例中找这个变量,如果没有找到,再去类中找;当通过实例为变量赋值时,无论类中有没有该同名变量,Python解释器都会创建一个同名实例变量。
2.1.5 构造方法
__init__()
该方法用来创建和初始化实例变量,这种方法是“构造方法”,也属于魔法方法。定义时,它的第一个参数应该是self,其后的参数才是用来初始化实例变量的。调用构造方法时不需要传入self。
例如:
class Animal(object):
'''定义动物类'''
def __init__(self,age,sex = 1,weight =0.0):
self.age = age #定义年龄实例变量
self.sex = sex #定义性别实例变量
self.weight = weight #定义体重实例变量
dog1 = Animal(2,1,5)
dog2 = Animal(1,weight = 10)
dog3 = Animal(1,sex = 1)
print('dog1年龄: {0},性别:{1},体重:{2}'.format(dog1.age,dog1.sex,dog1.weight))
print('dog2年龄: {0},体重:{1}'.format(dog2.age,dog2.weight))
print('dog3年龄: {0},性别:{1}'.format(dog3.age,dog3.sex))
dog1年龄: 2,性别:1,体重:5
dog2年龄: 1,体重:10
dog3年龄: 1,性别:1
不需要传入self,只需要提供后面三个实际参数。
2.1.6 实例方法
实例方法和实例对象都是某个实例所特有的。
方法是在类中定义的函数。而定义实例方法时它的第一个参数也应该是self,这个过程是将当前实例与该方法绑定起来,使之成为实例方法。
例如:
class Animal(object):
'''定义动物类'''
def __init__(self,age,sex = 1,weight =0.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(2,0,10.0)
print('a1体重:{0:0.2f}'.format(a1.weight))
a1.eat()
print('a1体重:{0:0.2f}'.format(a1.weight))
a1.run()
print('a1体重:{0:0.2f}'.format(a1.weight))
a1体重:10.00
eat...
a1体重:10.05
run...
a1体重:10.04
2.1.7 类方法
“类方法”与“类变量”类似,属于类而不属于个体实例方法,类方法不需要与实力绑定,但需要与类绑定,定义是它的第一个参数不是self,而是类的type实例。
例如:
class Account:
'''定义银行账户类'''
interest_rate = 0.0668
def __init__(self,owner,amount):
self.owner = owner #定义实例变量账户名
self.amount = amount #定义实例变量账户金额
@classmethod
def interest_by(cls,amt):
return cls.interest_rate * amt
interest = Account.interest_by(12000.0)
print('计算利息:{0:0.4f}'.format(interest))
执行结果:
计算利息:801.6000
定义类方法有两个关键:第一,方法的第一个参数cls是type类型的一个实例;第二,方法使用装饰器@classmethod声明该方法是类方法。
注意:类方法可以访问类变量和其他类方法,但不能访问其他实例方法和实例变量
2.1.8 静态方法
如果定义的方法既不想与实例绑定,也不想与类绑定,只是想把类作为它的命名空间,那么可以用静态发方法。
例如:
class Account:
'''定义银行账户类'''
interest_rate = 0.0668
def __init__(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(12000.0)
interest2 = Account.interest_with(12000.0)
print('计算利息:{0:0.4f}'.format(interest1))
print('计算利息:{0:0.4f}'.format(interest2))
执行结果为:
计算利息:801.6000
计算利息:801.6000
使用了@staticmethod装饰器,声明该方法是静态方法,方法参数不需要指定self和csl。
3.1封装性
3.1.1 私有变量
默认情况下Python中的变量是共有的,可以在类的外部访问它们。如果想让他们成为私有变量,可以在变量前加上双下划线"__"。
例如:
class Animal(object):
'''定义动物类'''
def __init__(self,age,sex = 1,weight =0.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(2,0,10.0)
print('a1体重:{0:0.2f}'.format(a1.weight))
a1.eat()
print('a1体重:{0:0.2f}'.format(a1.weight))
a1.run()
print('a1体重:{0:0.2f}'.format(a1.weight))
运行结果如下:
Traceback (most recent call last):
File "C:/Users/SZN/Desktop/test.py", line 18, in <module>
print('a1体重:{0:0.2f}'.format(a1.weight))
AttributeError: 'Animal' object has no attribute 'weight'
__weight变量在类内部访问没有问题,但是如果在外部访问则会发生错误。
注意:如果想在外部访问私有变量也是可以的,只需要将访问的格式改为“_类名___变量”。所以将上述代码a1.weight改为a1_Animal__weight
就可以访问了。但是及不推荐这样做,会破坏封装性
3.1.2 私有方法
原理同私有变量。
举个例子:
class Animal(object):
'''定义动物类'''
def __init__(self,age,sex = 1,weight =0.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(2,0,10.0)
print('a1体重:{0:0.2f}'.format(a1.weight))
a1.eat()
print('a1体重:{0:0.2f}'.format(a1.weight))
a1.run()
print('a1体重:{0:0.2f}'.format(a1.weight))
运行结果如下:
eat...
Traceback (most recent call last):
File "C:/Users/SZN/Desktop/test.py", line 21, in <module>
a1.run()
AttributeError: 'Animal' object has no attribute 'run'
3.1.3 定义属性
封装通常是对成员变量的封装。在严格意义上的面对对象设计中,一个类是不应该有共有实例成员变量的,这些实例成员变量应该被设计成为私有的,然后通过共有的setter和getter访问器访问。
但是访问器形式封装在编写代码时比较麻烦。为了解决这个问题,Python中提供了属性,定义属性可以使用@property和@属性名.setter装饰器。@property用来修饰getter访问器,@属性名.setter用来修饰setter访问器。
例如:
class Animal(object):
'''定义动物类'''
def __init__(self,age,sex = 1,weight =0.0):
self.age = age #定义年龄实例变量
self.sex = sex #定义性别实例变量
self.__weight = weight #定义体重实例变量
@property
def weight(self):
return self.__weight
@weight.setter
def weight(self,weight):
self.__weight = weight
运行结果:
a1体重:10.00
a1体重:123.45
4.1 继承性
4.1.1 继承性的概念
上栗子:
class Animal(object):
'''定义动物类'''
def __init__(self,age,sex = 1,weight =0.0):
self.age = age #定义年龄实例变量
self.sex = sex #定义性别实例变量
self.weight = weight #定义体重实例变量
class Dog(Animal):
'''继承动物类'''
def __init__(self,name,age,sex = 1,weight = 0.0):
super().__init__(age,sex,weight)
self.name = name
dog1 = Dog('Lucky',2,1,5)
dog2 = Dog('Bob',1,weight = 10)
dog3 = Dog('Lucy',1,sex = 1)
print('{0}年龄: {1},性别:{2},体重:{3}'.format(dog1.name,dog1.age,dog1.sex,dog1.weight))
print('{0}年龄: {1},体重:{2}'.format(dog2.name,dog2.age,dog2.weight))
print('{0}年龄: {1},性别:{2}'.format(dog3.name,dog3.age,dog3.sex))
运行结果:
Lucky年龄: 2,性别:1,体重:5
Bob年龄: 1,体重:10
Lucy年龄: 1,性别:1
Dog类继承Animal类,其中小括号中的是父类,如果没有指明父类(一对空的小括号或者省略小括号),则默认父类为object,object类是Python的根类。子类中定义构造方法时首先要调用父类的构造方法,初始化父类变量。super().__init__(age,sex,weight)
就是调用父类的构造方法,super()函数时返回父类的引用,通过它可以调用父类中的实例变量和方法。
注意:子类继承父类时只是继承父类中公有的成员变量和方法,不能继承私有的成员变量和方法。
4.1.2 重写方法
如果子类方法名与父类方法名相同,而参数列表也相同,只是方法体不同,那么子类重写了父类的方法。
例如:
class Animal(object):
'''定义动物类'''
def __init__(self,age,sex = 1,weight =0.0):
self.age = age #定义年龄实例变量
self.sex = sex #定义性别实例变量
self.weight = weight #定义体重实例变量
def eat(self):
self.weight += 0.05
print('Animal eat...')
class Dog(Animal):
'''继承动物类'''
def __init__(self,name,age,sex = 1,weight = 0.0):
super().__init__(age,sex,weight)
self.name = name
def eat(self):
self.weight += 0.03
print('Dog eat...')
dog1 = Dog('Lucky',2,1,5)
dog1.eat()
运行结果:
Dog eat...
可见子类重写了父类的方法,通过子类实例调用eat()方法时,会调用子类重写的eat()。
4.1.3 多继承性
就是一个子类可以有多个父类。当子类实例调用一个方法时,先从子类中查找,如果没有找到则查找父类。父类的查找顺序是按照子类声明的父类列表从左往右查找,如果没有找到再找父类的父类,依次查找下去。
例如:
class ParentClass1:
def run(self):
print('ParentClass1 run...')
class ParentClass2:
def run(self):
print('ParentClass2 run...')
class SubClass1(ParentClass2,ParentClass1):
pass
sub1 = SubClass1()
sub1.run()
ParentClass2 run...
5.1 多态性
5.1.1 多态概念
发生多态要有两个前提条件:
继承——多态发生一定是再子类和父类之间。
重写——子类重写父类的方法。
5.1.2 类型检查
运行期类型检查使用isinstance(object,classinfo)函数,它可以检查object实例是否由classinfo类或classinfo子类所创建的实例。
class Animal(object):
'''定义动物类'''
def __init__(self,age,sex = 1,weight =0.0):
self.age = age #定义年龄实例变量
self.sex = sex #定义性别实例变量
self.weight = weight #定义体重实例变量
def eat(self):
self.weight += 0.05
print('Animal eat...')
def run(self):
self.weight -= 0.01
print('Animal run...')
class Dog(Animal):
'''继承动物类'''
def __init__(self,name,age,sex = 1,weight = 0.0):
super().__init__(age,sex,weight)
self.name = name
def eat(self):
self.weight += 0.03
print('Dog eat...')
def run(self):
self.weight -= 0.02
print('Dog run...')
class Cat(Animal):
'''继承动物类'''
def __init__(self,name,age,sex = 1,weight = 0.0):
super().__init__(age,sex,weight)
self.name = name
def eat(self):
self.weight += 0.03
print('Cat eat...')
def run(self):
self.weight -= 0.02
print('Cat run...')
a1 = Animal(2,0,10.0)
a2 = Dog(2,0,10.0)
a3 = Cat(2,0,10.0)
print(isinstance(a1,Dog)) #没有发生多态
print(isinstance(a2,Dog)) #发生多态
print(isinstance(a3,Dog)) #没有发生多态
print(isinstance(a2,Animal)) #发生多态
运行结果为:
False
True
False
True
今天就学到这里了