专栏首页AzMarkPython 学习笔记之类「面向对象,超类,抽象」

Python 学习笔记之类「面向对象,超类,抽象」

对象魔法

在面向对象编程中,术语对象大致意味着一系列数据 (属性) 以及一套访问和操作这些数据的方法。

封装

封装讲究结构复用,逻辑内敛,以固定接口对外提供服务。其遵循单一职责,规定每个类型仅有一个引发变化的原因。单一封装的核心是解耦和内聚,这让设计更简单,清晰,代码更易测试和冻结,避免了不确定性。

继承

继承在遵循原有设计和不改变既有代码的前提下,添加新功能,或改进算法。其对应开闭原则,对修改封闭,对扩展开放。

多态

多态特性遵循里氏替换原则,所有继承子类应该能直接用于引用父类的场合。

我们习惯于将复杂类型的公用部分剥离出来,形成稳固的抽象类。其他引发变化的相似因素则被分离成多个子类,以确保单一职责得到遵守,并能相互替换。

我们习惯于将复杂类型的公用部分剥离出来,形成稳固的抽象类。其他引发变化的相似因素则被分离成多个子类,以确保单一职责得到遵守,并能相互替换。

场景:先将整体框架抽象成基类,然后每个子类仅保留单一分支逻辑。

继承和多态

定义一个名为 Animal 的 class,有一个 run() 方法可以直接打印:

class Animal(object):
   def run(self):
       print("Animal is running...")

当我们需要编写 Dog 和 Cat 类时,就可以直接从 Animal 类继承:

class Dog(Animal):
   pass
class Cat(Animal):
   pass

对于 Dog 来说,Animal 就是它的父类,对于 Animal 来说,Dog 就是它的子类。Cat 和 Dog 类似。

继承的好处一:子类获得了父类的全部功能,扩展子类自己的功能

上例中 Animial 实现了 run() 方法,因此,Dog 和 Cat 作为它的子类也拥有 run() 方法:

dog = Dog()
dog.run()

cat = Cat()
cat.run()

'''
Animal is running...
Animal is running...
'''

我们也可以扩展子类的方法,比如 Dog 类:

class Dog(Animal):
   def run(self):
       print("Dog is running...")
   def dog_eat(self):
       print("Eating bones...")

继承的好处二:重写父类的功能。

无论是 Dog 还是 Cat,它们 run() 的时候,显示的都是 Animal is running...,而对于 Dog 和 Cat 本身,应该具有自己的 run() 特性,即: Dog is running.. 和 Cat is running...,因此,对 Dog 和 Cat 类改进如下:

class Dog(Animal):
    def run(self):
        print("Dog is running...")
    def dog_eat(self):
        print("Eating bone...")

class Cat(Animal):
    def run(self):
        print("Cat is running...")
    def cat_eat(self):
        print("Eating fish...")

if __name__ == "__main__":
    dog = Dog()
    dog.run()

    cat = Cat()
    cat.run()
'''
Dog is running...
Eating bone...
Cat is running...
Eating fish...
'''

当子类的 run() 覆盖了父类的 run() 时候,运行时总是会调用子类的 run() 。这样,我们就获得了继承的另一个好处:多态。

判断一个变量是否是某个类型可以用 isinstance() 判断:

>>> isinstance(dog,Animal)
>>> isinstance(cat,Animal)
'''
True
True
'''

>>> isinstance(dog,Dog)
>>> isinstance(cat,Dog)
'''
True
False
'''

上面示例可以看到,dog 的数据类型既是 Animal,也是 Dog。因为 Dog 是从 Animal 继承下来的,当我们创建 Dog 实例时,我们认为 dog 的数据类型是 Dog,但同时 dog 也是 animal 的数据类型,因为 Dog 本来就是 Animal 的一种。而 cat 的数据类型是 Animal 没错,但是 cat 不是 Dog 数据类型。

所以,在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行:

>>> b = Animal()
>>> isinstance(b, Dog)
'''
False
'''

Dog 可以看成 Animal,但 Animal 不可以看成Dog。

超类

要指定超类,可在 class 语句中的类名后加上超类名,并将其用圆括号括起。

Filter 是一个过滤序列的通用类。实际上,它不会过滤掉任何东西。

class Filter:
    def init(self):
        self.blocked = []
    def filter(self, sequence):
        return [x for x in sequence if x not in self.blocked]

class SPAMFilter(Filter): 
    # SPAMFilter是Filter的子类 def init(self): # 重写超类Filter的方法init
    def init(self): # 重写超类Filter的方法init
        self.blocked = ['SPAM']
        
if __name__=="__main__":
    f = Filter()
    f.init()
    print(f.filter([1, 2, 3]))
    
'''
1, 2, 3
'''

Filter 类的用途在于可用作其他类 (如将 'SPAM' 从序列中过滤掉的 SPAMFilter 类) 的基类 (超类)。

if __name__=="__main__":
   s = SPAMFilter() 
   s.init()
   a = s.filter(['SPAM', 'SPAM', 'SPAM', 'SPAM', 'eggs', 'bacon', 'SPAM'])
   print(a)
'''
['eggs', 'bacon']
'''

请注意 SPAMFilter 类的定义中有两个要点。

  • 以提供新定义的方式重写了 Filter 类中方法 init 的定义。
  • 直接从 Filter 类继承了方法 filter 的定义,因此无需重新编写其定义。

第二点说明了继承很有用的原因:可以创建大量不同的过滤器类,它们都从 Filter 类派生而来,并且都使用已编写好的方法 filter。

你可以用复数形式的 __ bases __ 来获悉类的基类,而基类可能有多个。为说明如何继承多个类,下面来创建几个类。

class Calculator:
    def calculate(self, expression):
        self.value = eval(expression)
class Talker:
    def talk(self):
        print('Hi, my value is',self.value)
class TalkingCalculator(Calculator, Talker): pass

if __name__=="__main__":
    tc = TalkingCalculator()
    tc.calculate('1 + 2 * 3')
    tc.talk()
   
'''
Hi, my value is 7
'''

这被称为多重继承,是一个功能强大的工具。然而,除非万不得已,否则应避免使用多重继承,因为在有些情况下,它可能带来意外的 “并发症”。

使用多重继承时,有一点务必注意:如果多个超类以不同的方式实现了同一个方法 (即有多个同名方法),必须在class 语句中小心排列这些超类,因为位于前面的类的方法将覆盖位于后面的类的方法。

因此,在前面的示例中,如果 Calculator 类包含方法 talk,那么这个方法将覆盖 Talker 类的方法 talk (导致它不可访问)。

如果像下面这样反转超类的排列顺序:

class TalkingCalculator(Talker, Calculator): pass

将导致 Talker 的方法 talk 是可以访问的。多个超类的超类相同时,查找特定方法或属性时访问超类的顺序称为方法解析顺序 (MRO),它使用的算法非常复杂。

抽象基类

一般而言,抽象类是不能实例化的类,其职责是定义子类应实 现的一组抽象方法。

下面是一个简单的示例:

from abc import ABC, abstractmethod
class Talker(ABC): 
   @abstractmethod 
   def talk(self):
       pass

这里的要点是你使用 @abstractmethod 来将方法标记为抽象的 —— 在子类中必须实现的方法。

如果你使用的是较旧的 Python 版本,将无法在模块 abc 中找到 ABC 类。在这种情况下,需要导入ABCMeta,并在类定义开头包含代码行 __ metaclass __ = ABCMeta (紧跟在 class 语句后面并缩进)。如果你使用的是 3.4 之前的 Python 3 版本,也可使用 Talker(metaclass=ABCMeta) 代替 Talker(ABC)。

抽象类(即包含抽象方法的类)最重要的特征是不能实例化。

>>> Talker()
   Traceback (most recent call last): 
   File "<stdin>", line 1, in <module>
  TypeError: Can't instantiate abstract class Talker with abstract methods talk

假设像下面这样从它派生出一个子类:

class Knigget(Talker): 
   pass

由于没有重写方法 talk,因此这个类也是抽象的,不能实例化。如果你试图这样做,将出现类似于前面的错误消息。然而,你可重新编写这个类,使其实现要求的方法。

class Knigget(Talker): 
   def talk(self):
       print("Ni!")

现在实例化它没有任何问题。这是抽象基类的主要用途,而且只有在这种情形下使用 isinstance 才是妥当的:如果先检查给定的实例确实是 Talker 对象,就能相信这个实例在需要的情况下有方法 talk。

>>> k = Knigget()
>>> isinstance(k, Talker) True
>>> k.talk()
Ni!

总结

将相关的东西放在一起。如果一个函数操作一个全局变量,最好将它们作为一个类的属性和方法。

不要让对象之间过于亲密。方法应只关心其所属实例的属性,对于其他实例的状态,让它们自己去管理就好了。

慎用继承,尤其是多重继承。继承有时很有用,但在有些情况下可能带来不必要的复杂性。要正确地使用多重继承很难,要排除其中的 bug 更难。

保持简单,让方法短小紧凑。一般而言,应确保大多数方法都能在 30 秒内读完并理解。

好书推荐

PS:以后的每一篇文章我都会推荐一本好的技术类书籍和一本成长类书籍,目前仅限于 PDF,为了激励自己不断学习新知识,坚持阅读,希望读者们能跟我一起行动起来。

Python 基础教程(第3版)

这是我最近在阅读的书,从整体上来看,讲得很通俗易懂,是很好的 Python 入门读物,并且已经升级到 Python 3。

豆瓣简介

本书是经典教程的全新改版,作者根据 Python 3.0 版本的种种变化,全面改写了书中内容,做到既能 “瞻前” 也能 “顾后”。本书层次鲜明、结构严谨、内容翔实,特别是在最后几章,作者将前面讲述的内容应用到了10个引人入胜的项目中,并以模板的形式介绍了项目的开发过程。本书既适合初学者夯实基础,又能帮助 Python 程序员提升技能,即使是 Python 方面的技术专家,也能从书里找到令你耳目一新的东西。

世界如此之大,做一个什么样的自己完全取决于你的选择。艾力一直坚信,梦想和现实的距离并不遥远。努力奋斗,你我你现在是什么职位,从事什么工作?谢谢支持

想要书籍的请在后台留言 【180724】获取这两本书的 PDF 版,我相信如果读了我简书的文章,应该会对你有所感悟,我会尽量推荐自己读过的好书。

本文分享自微信公众号 - Python梦工厂(AzMark950831)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-07-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 或对,或错?

    公号许久没更文了,这段时间以来,自己在面临着一些抉择。未曾想过,毕业后一个月考虑的事情比大学四年加起来的还要多。也许是大学过得太安逸了,欠的债全部攒到毕业后来还...

    Python技术与生活认知的分享
  • Python 学习之面向对象「下」

    Python技术与生活认知的分享
  • To be a better man !

    Python技术与生活认知的分享
  • Android M 特性 Doze and App Standby模式详解

    从Android6.0开始,Android提供了两种省电延长电池寿命的功能:Doze和App Standby;表现形式:当设备没有连接到电源,设备进入Doze模...

    QQ空间开发团队
  • orm

    对象关系映射(Object Relational Mapping,简称ORM)是通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到...

    GH
  • 【help of python】ones

    py3study
  • leetcode: 45. Jump Game II

    JNingWei
  • Python3 数据库增删改查简单操作

    conn=pymysql.connect(host='127.2.2.2',user='root',passwd='123456',db='records')

    py3study
  • 「企业架构」应用架构概述

    应用架构描述了业务中使用的应用程序的行为,重点是它们如何相互之间以及如何与用户交互。它关注的是应用程序消费和生成的数据,而不是它们的内部结构。在应用程序组合管理...

    首席架构师智库
  • QMake study(part 3)

    举例来说,如果你在Windows下使用Microsoft Visual Studio,然后你需要把QMAKESPEC环境变量设置为win32-msvc。如果你在...

    py3study

扫码关注云+社区

领取腾讯云代金券