Python 面向对象编程(上篇)

Python实战社群

Java实战社群

长按识别下方二维码,按需求添加

扫码关注添加客服

进Python社群▲

扫码关注添加客服

进Java社群

作者丨zhenguo 

来源丨Python与算法社区

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属性已经变为类内私有,不会暴露在外面。

总结: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 多态

  • 总结

程序员专栏 扫码关注填加客服 长按识别下方二维码进群

近期精彩内容推荐:   你的代码将永久封存于北极地底1000年! 中国程序员VS美国程序员,太形象了... 30个极简Python代码,拿走即用 同事问我MySQL怎么递归查询,我懵逼了...


在看点这里好文分享给更多人↓↓

你可能感兴趣的:(多态,编程语言,面向对象编程,类,封装)