阅读量

原创教程,严禁转载。引用本文,请署名 Python中文网, http://www.zglg.work


八、Python 面向对象编程(上篇)

面向对象程序设计思想,首先思考的不是程序执行流程,它的核心是抽象出一个对象,然后构思此对象包括的数据,以及操作数据的行为方法。

1 类定义

动物是自然界一个庞大的群体,以建模动物类为主要案例论述OOP编程。

Python语言创建动物类的基本语法如下,使用class关键字定义一个动物类:

class Animal():
    pass

类里面可包括数据,如下所示的Animal类包括两个数据:self.nameself.speed

class Animal():
   def __init__(self,name,speed):
       self.name = name # 动物名字
       self.speed = speed # 动物行走或飞行速度

注意到类里面通过系统函数__init__为类的2个数据赋值,数据前使用self保留字。

self的作用是指名这两个数据是实例上的,而非类上的。

同时注意到__init__方法的第一个参数也带有self,所以也表明此方法是实例上的方法。

2 对象或实例

理解什么是实例上的数据或方法,什么是类上的数据,需要先建立对象或实例的概念,的概念,如下:

# 生成一个名字叫加菲猫、行走速度8km/h的cat对象
cat = Animal('加菲猫',8) 

cat就是Animal类的对象或实例,也可以一次创建成千上百个实例,如下创建1000只蜜蜂:

bees = [Animal('bee'+str(i),5) for i in range(1000)]

总结:自始至终只使用一个类Animal,但却可以创建出许多个它的对象,因此是一对多的关系。

对象创建完成后,下一步打印它看看:

In [1]: print(cat)                                                           
<__main__.Animal object at 0x7fce3a596ad0>

结果显示它是Animal对象,其实打印结果显示对象属性信息会更友好,那么怎么实现呢?

3 打印实例

只需重新定义一个系统(又称为魔法)函数__str__ ,就能让打印实例显示的更加友好:

class Animal():
   def __init__(self,name,speed):
       self.name = name # 动物名字
       self.speed = speed # 动物行走或飞行速度

   def __str__(self):
        return '''Animal({0.name},{0.speed}) is printed
                name={0.name}
                speed={0.speed}'''.format(self)

使用0.数据名称的格式,这是类专有的打印格式。

现在再打印:

cat = Animal('加菲猫',8)
print(cat)

打印信息如下:

Animal(加菲猫,8) is printed
                name=加菲猫
                speed=8

以上就是想要的打印格式,看到实例的数据值都正确。

4 属性

至此,我们都称类里的namespeed为数据,其实它们有一个专业名称:属性。

同时,上面还有一个问题我们没有回答完全,至此已经解释了实例的属性。那么什么是类上的属性?

如下,在最新Animal类定义基础上,再添加一个cprop属性,它前面没有self保留字:

class Animal():
   cprop = "我是类上的属性cprop"

   def __init__(self,name,speed):
       self.name = name # 动物名字
       self.speed = speed # 动物行走或飞行速度

   def __str__(self):
        return '''Animal({0.name},{0.speed}) is printed
                name={0.name}
                speed={0.speed}'''.format(self)

类上的属性直接使用类便可引用:

In [1]: Animal.cprop                                                           
Out[1]: '我是类上的属性cprop'

类上的属性,实例同样可以引用,并且所有的实例都共用此属性值:

In [1]: cat = Animal('加菲猫',8)
In [2]: cat.cprop                                                              
Out[2]: '我是类上的属性cprop'

Python作为一门动态语言,支持属性的动态添加和删除。

如下cat实例原来不存在color属性,但是赋值时不光不会报错,相反会直接将属性添加到cat上:

cat.color = 'grap'

那么,如何验证cat是否有color属性呢?使用内置函数hasattr

In [24]: hasattr(cat,'color') # cat 已经有`color`属性                          
Out[24]: True

但是注意:以上添加属性方法仅仅为cat对象本身添加,而不会为其他对象添加:

In [26]: monkey = Animal('大猩猩',2)                                            
In [27]: hasattr(monkey,'color')                                             
Out[27]: False

monkey实例并没有color属性,注意与__init__创建属性方法的区别。

5 private,protected,public

namespeed属性,引用此实例的对象都能访问到它们,如下:

# 模块名称:manager.py

import time

class Manager():
    def __init__(self,animal):
        self.animal = animal

    def recordTime(self):
        self.__t = time.time()
        print('feeding time for %s(行走速度为:%s) is %.0f'%
        (self.animal.name,self.animal.speed,self.__t))

    def getFeedingTime(self):
        return '%0.f'%(self.__t,) 

使用以上Manager类,创建一个cat实例,xiaoming实例引用cat:

cat = Animal('加菲猫',8)
xiaoming =  Manager(cat) 

xiaomingrecordTime方法引用里,引用了animal的两个属性namespeed:

In[1]: xiaoming.recordTime()

Out[1]: feeding time for 加菲猫(行走速度为:8) is 1595681304

注意看到self.__t属性,它就是一个私有属性,只能被Manager类内的所有方法引用,如被方法getFeedingTime方法引用。但是,不能被其他类引用。

如果我们连speed这个属性也不想被其他类访问,那么只需稍作下Animal类,将self.speed修改为self.__speed:

同时修改Manager类的self.animal.speed修改为self.animal.__speed,再次调用下面方法时:

xiaoming.recordTime()

就会报没有__speed属性的异常,从而验证了__speed属性已经变为类内私有,不会暴露在外面。

总结:name属性相当于java的public属性,而__speed相当于java的private属性。

6 继承

上面已经讲完了OOP三大特性中的封装性,而继承是它的第二大特性。子类继承父类的所有publicprotected数据和方法,极大提高了代码的重用性。

如上我们创建的Animal类最新版本为:

class Animal():
   cprop = "我是类上的属性cprop"

   def __init__(self,name,speed):
       self.name = name # 动物名字
       self.__speed = speed # 动物行走或飞行速度

   def __str__(self):
        return '''Animal({0.name},{0.__speed}) is printed
                name={0.name}
                speed={0.__speed}'''.format(self)

现在有个新的需求,要重新定义一个Cat猫类,它也有namespeed两个属性,同时还有colorgenre两个属性,打印时只需要打印namespeed两个属性就行。

因此,基本可以复用基类Animal,但需要修改__speed属性为受保护(protected)的_speed属性,这样子类都可以使用此属性,而外部还是访问不到它。

综合以上,Cat类的定义如下:

class Cat(Animal):
    def __init__(self,name,speed,color,genre):
        super().__init__(name,speed)
        self.color = color 
        self.genre = genre

首先使用super()方法找到Cat的基类Animal,然后引用基类的__init__方法,这样复用了基类的此方法。

使用Cat类,打印时,又复用了基类的 __str__方法:

jiafeimao = Cat('加菲猫',8,'gray','CatGenre')
print(jiafeimao)

打印结果:

Animal(加菲猫,8) is printed
                name=加菲猫
                speed=8

以上就是基本的继承使用案例,继承要求基类定义的数据和行为尽量标准、尽量精简,以此提高继承的复用性。

7 多态

如果说OOP的封装和继承使用起来更加直观易用,那么作为第三大特性的多态,在实践中真正运用起来可就不那么容易。有的读者OOP编程初期,可能对多态的价值体会不深刻,甚至都已经淡忘它的存在。

那么问题就在:多态到底真的有用吗?到底使用在哪些场景?

多态价值很大,使用场景很多,几乎所有的系统或软件,都能看到它的应用。

这篇文章,我会尽可能通过一个精简的例子说明它的价值和使用方法。

使用对比法,如果不用多态,方法怎么写;使用多态,又是怎么写。

为了一脉相承,做到一致性,仍然基于上面的案例,已经创建好的Cat类要有一个方法打印和返回它的爬行速度。同时需要再创建一个类Bird,要有一个方法打印和返回它的飞行速度;

如果不使用多态,可能会这样写:

对于Cat类只需新增一个方法:

class Cat(Animal):
    def __init__(self,name,speed,color,genre):
        super().__init__(name,speed)
        self.color = color 
        self.genre = genre
    # 添加方法
    def getRunningSpeed(self):
        print('running speed of %s is %s' %(self.name, self._speed))
        return self._speed

重新创建一个Bird类:

class Bird(Animal):
    def __init__(self,name,speed,color,genre):
        super().__init__(name,speed)
        self.color = color 
        self.genre = genre
    # 添加方法
    def getFlyingSpeed(self):
        print('flying speed of %s is %s' %(self.name, self._speed))
        return self._speed

最后,上面创建的Manager类会引用CatBird类,但是需要修改recordTime方法,因为一个为Cat它是爬行的,Bird它是飞行的,所以要根据类型不同做逻辑区分,如下所示:

# 模块名称:manager.py

import time
from animal import (Animal,Cat,Bird)

class Manager():
    def __init__(self,animal):
        self.animal = animal

    def recordTime(self):
        self.__t = time.time()
        if isinstance(self.animal, Cat):
            print('feeding time for %s is %.0f'%(self.animal.name,self.__t))
            self.animal.getRunningSpeed()
        if isinstance(self.animal,Bird):
            print('feeding time for %s is %.0f'%(self.animal.name,self.__t))
            self.animal.getFlyingSpeed()

    def getFeedingTime(self):
        return '%0.f'%(self.__t,) 

如果再来一个类,我们又得需要修改recordTime,再增加一个if分支,从软件设计角度讲,这种不断破坏原来类方法的行为不可取。

但是,使用多态,就可以保证recordTime不被修改,不必写很多if分支。

怎么来实现呢?一种实现方法:首先在基类Animal中创建一个基类方法,然后CatBird分别重写此方法,最后传入到Manager类的animal参数是什么类型,在recordTime方法中就会对应调用这个animal实例的方法,这就是多态

代码如下:

animal2.py 模块如下:

# animal2.py 模块

class Animal():
   cprop = "我是类上的属性cprop"

   def __init__(self,name,speed):
       self.name = name # 动物名字
       self._speed = speed # 动物行走或飞行速度

   def __str__(self):
        return '''Animal({0.name},{0._speed}) is printed
                name={0.name}
                speed={0._speed}'''.format(self)

   def getSpeedBehavior(self):
       pass 

class Cat(Animal):
    def __init__(self,name,speed,color,genre):
        super().__init__(name,speed)
        self.color = color 
        self.genre = genre

    # 添加方法
    def getSpeedBehavior(self):
        print('running speed of %s is %s' %(self.name, self._speed))
        return self._speed


class Bird(Animal):
    def __init__(self,name,speed,color,genre):
        super().__init__(name,speed)
        self.color = color 
        self.genre = genre

    # 添加方法
    def getSpeedBehavior(self):
        print('flying speed of %s is %s' %(self.name, self._speed))
        return self._speed

manager2.py 模块如下:

# manager2.py 模块

import time
from animal2 import (Animal,Cat,Bird)

class Manager():
    def __init__(self,animal):
        self.animal = animal

    def recordTime(self):
        self.__t = time.time()
        print('feeding time for %s is %.0f'%(self.animal.name,self.__t))
        self.animal.getSpeedBehavior()

    def getFeedingTime(self):
        return '%0.f'%(self.__t,)  

recordTime方法非常清爽,不需要任何if逻辑,只需要调用我们定义的Animal类的基方法getSpeedBehavior即可。

在使用上面所有类时,Manager(jiafeimao)传入Cat类实例时,recordTime方法调用就被自动指向Cat实例的getSpeedBehavior方法;

Manager(haiying)传入Bird类实例时,自动指向Bird实例的getSpeedBehavior方法,这就是多态和它的价值。


if __name__ == "__main__":
    jiafeimao = Cat('jiafeimao',2,'gray','CatGenre')
    haiying = Bird('haiying',40,'blue','BirdGenre')

    Manager(jiafeimao).recordTime()
    print('#'*30)
    Manager(haiying).recordTime()  

Python 20个专题完整目录:

Python前言

Google Python代码风格指南

Python数字

Python正则之提取正整数和大于0浮点数

Python字符串

CSV读写乱码问题

Unicode标准化

Unicode, UTF-8, ASCII

Python动态生成变量

Python字符串对齐

Python小项目1:文本句子关键词的KWIC显示

Python列表

Python流程控制

Python编程习惯专题

Python函数专题

Python面向对象编程-上篇

Python面向对象编程-下篇

Python十大数据结构使用专题

Python包和模块使用注意事项专题

Python正则使用专题

Python时间专题

Python装饰器专题

Python迭代器使用专题

Python生成器使用专题

Python 绘图入门专题

Matplotlib绘图基础专题

Matplotlib绘图进阶专题

Matplotlib绘图案例

NumPy图解入门