美文网首页
Python 学习笔记之类「面向对象,超类,抽象」

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

作者: 随时学丫 | 来源:发表于2018-07-24 13:54 被阅读70次

    一、对象魔法

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

    封装

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

    继承

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

    多态

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

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

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

    继承和多态

    定义一个名为 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 秒内读完并理解。

    推荐阅读

    Python 学习笔记之类与实例

    好书推荐

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

    Python 基础教程(第3版)

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

    豆瓣简介

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

    python基础教程3.jpg

    你一年的8760小时(升级版)

    这本书是我在大学读的,对我改变很大,学会了坚持早睡早起,坚持锻炼和学英语。艾力老师带领了很多团帮助我们摆脱懒癌,共同进步。由于对这本书情有独钟,我还在简书写过一篇文章讲述我是如果通过一本书学会早起的。

    早起 - 对我影响最大的习惯

    豆瓣简介

    《你一年的8760小时》自出版以来,连续几个月位于各大排行榜前列,并有幸荣获 “2015 年京东励志年度新书榜 TOP1”。正是读者的支持,让他成了一个真正意义上的写作者。

    世界如此之大,做一个什么样的自己完全取决于你的选择。艾力一直坚信,梦想和现实的距离并不遥远。努力奋斗,你终将成为理想中的自己。

    你一年的8760小时1.jpeg

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

    Python梦工厂

    相关文章

      网友评论

          本文标题:Python 学习笔记之类「面向对象,超类,抽象」

          本文链接:https://www.haomeiwen.com/subject/okwemftx.html