欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Python 面向对象编程(上篇)

程序员文章站 2022-05-08 08:44:44
...

Python 面向对象编程(上篇)

我的施工计划图

已完成专题包括:

1我的施工计划

2数字专题

3字符串专题

4列表专题

5流程控制专题

6编程风格专题

7函数使用专题

今天是面向对象编程的上篇:基础专题

Python 面向对象编程

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

本专题主要讨论面向对象编程(OOP)的基础和进阶知识,实际开发模型中OOP的主要实践,尽量使用最贴切的例子。

基础专题

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这个属性也不想被其他类访问,那么只需将self.speed修改为self.__speed:

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

xiaoming.recordTime()

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

Python 面向对象编程(上篇)

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

下面在说继承时,讲解protected属性,实际上它就是带有1个_的属性,它只能被继承的类所引用。

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方法,这就是多态和它的价值,Manager类的方法不必每次都修改,保证了类的封装性。


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

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

总结

以上就是面向对象编程专题的基础部分,大纲如下:

  • Python 面向对象编程

  • 基础专题

    • 1 类定义

    • 2 实例

    • 3 打印实例

    • 4 属性

    • 5 private,protected,public

    • 6 继承

    • 7 多态

  • 总结

感谢华章出版社对原创文章的大力支持,赠送3本机器学习:算法视角(原书第2版)》,机器学习领域畅销教材知名媒体推荐的十大机器学习入门教材之一,作者都是名校教授大咖。

中奖读者综合考虑留言质量,参与度和赞赏情况,29日回复中奖留言。长按下方二维码查看此书详情,明天京东有满减活动。

Python 面向对象编程(上篇)

机器学习:算法视角

Python 面向对象编程(上篇)

长按下方二维码

全文4000多字,100%用心原创作品。欢迎点赞、在看和转发支持,这样我会更有动力写好下一篇OOP编程的进阶部分,谢谢。