温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Python面向对象,超类和抽象的概念是什么

发布时间:2022-01-17 17:23:49 来源:亿速云 阅读:218 作者:iii 栏目:互联网科技

本文小编为大家详细介绍“Python面向对象,超类和抽象的概念是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“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!

读到这里,这篇“Python面向对象,超类和抽象的概念是什么”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注亿速云行业资讯频道。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI