Python面向对象要素-继承(Inheritance)

                                      作者:尹正杰

版权声明:原创作品,谢绝转载!否则将追究法律责任。

一.继承概述

1>.基本概念

  前面我们学习了Python的面向对象三要素之一,封装。今天我们来学习一下继承(Inheritance)

  人类和猫类都继承自动物类。

  个体继承自父类,继承了父类的一部分特征,但也可以有自己的个性。

  再面向对象的世界中,从父类继承,就可以直接拥有父类的属性方法,这样可以减少代码,多复用。子类可以定义自己的属性和方法。

2>.看一个不用继承的例子

 #!/usr/bin/env python
#_*_conding:utf-8_*_
#@author :yinzhengjie
#blog:http://www.cnblogs.com/yinzhengjie class Animal:
def shout(self):
print("Animal shouts") class Cat:
def shout(self):
print("Cat shouts") a = Animal()
a.shout() c = Cat()
c.shout() #以上代码执行结果如下:
Animal shouts
Cat shouts

3>. 使用继承的方式改良上一个不用继承的案例

 #!/usr/bin/env python
#_*_conding:utf-8_*_
#@author :yinzhengjie
#blog:http://www.cnblogs.com/yinzhengjie class Animal:
def __init__(self,name):
self._name = name def shout(self): #定义一个通用的吃方法
print("{} shouts".format(self.__class__.__name__)) @property
def name(self):
return self._name class Cat(Animal):
pass class Dog(Animal):
pass a = Animal("monster")
a.shout() cat = Cat("Kitty")
cat.shout()
print(cat.name) dog = Dog("二哈")
dog.shout()
print(dog.name) #以上代码执行结果如下:
Animal shouts
Cat shouts
Kitty
Dog shouts
二哈
 #!/usr/bin/env python
#_*_conding:utf-8_*_
#@author :yinzhengjie
#blog:http://www.cnblogs.com/yinzhengjie class Document:
def __init__(self,content):
self.content = content def print(self): #基类中只定义,不实现的方法,称为“抽象方法”。在python中,如果采用这种方式定义的抽象方法,子类可以不实现,知道子类使用该方法的时候才报错。
"""
基类提供的方法可以不具体实现,因为它未必适合子类的打印,子类中需要覆盖重写。
"""
raise NotImplementedError() class Word(Document):
pass class Pdf(Document):
pass

Python中抽象方法的案例

4>.总结

  通过上例可以看出,通过继承,猫类,狗类不用写代码,直接继承了父类的属性和方法。

  继承:
    class Cat(Animal)这种形式就是从父类继承,括号中写上继承的类的列表。
    继承可以让子类从父类获取特征(属性和方法)   父类:
    calss Animal就是Cat和Dog的父类,也称为基类,超类。   子类:
    Cat就是Animal的子类,也成为派生类。

二.继承定义

1>.继承使用格式

通过上面的案例,相比大家也可以总结出来继承的使用格式:
  class 子类名(基类1[,基类2,...]) 和C++一样,Python也支持多继承,继承也可以多级。

2>.在Python3中,object类是所有对象的根基类

 #!/usr/bin/env python
#_*_conding:utf-8_*_
#@author :yinzhengjie
#blog:http://www.cnblogs.com/yinzhengjie class A:
pass #如果类定义时,没有基类列表,等同于继承自object。
class A(object):
pass """
注意,上例在Python2中,两种写法时不同的。
Python支持多继承,继承也可以多级。
"""

3>.查看继承的特殊属性和方法

 #!/usr/bin/env python
#_*_conding:utf-8_*_
#@author :yinzhengjie
#blog:http://www.cnblogs.com/yinzhengjie class A:
pass print(A.__base__) #类的基类。
print(A.__bases__) #类的基类元组。
print(A.__mro__) #显示方法查找顺序,基类的元组。
print(A.mro()) #同上,返回列表。
print(A.__subclasses__()) #类的子类列表。 #以上代码执行结果如下:
<class 'object'>
(<class 'object'>,)
(<class '__main__.A'>, <class 'object'>)
[<class '__main__.A'>, <class 'object'>]
[]

三.继承中的访问控制

1>.代码案例

 #!/usr/bin/env python
#_*_conding:utf-8_*_
#@author :yinzhengjie
#blog:http://www.cnblogs.com/yinzhengjie """
从父类继承,自己没有的,就可以到父类中找。
私有的都是不可以访问的,但是本质上依然是改了名称放在这个属性所在类的实例"__dict__"中。
知道这个新名称就可以直接找到这个隐藏的变量,这是个黑魔法技巧,慎用。
""" class Animal:
__COUNT = 100
HEIGHT = 0 def __init__(self,age,weight,height):
self.__COUNT += 1
self.age = age
self.__weight = weight
self.HEIGHT = height def eat(self):
print("{} eat".format(self.__class__.__name__)) def __getweight(self):
print(self.__weight) @classmethod
def showcount1(cls):
print(cls)
print(cls.__dict__)
print(cls.__COUNT) @classmethod
def __showcount2(cls):
print(cls.__COUNT) def showcount3(self):
print(self.__COUNT) class Cat(Animal):
NAME = 'CAT'
__COUNT = 200 c = Cat(3,5,15)
c.eat()
print(c.HEIGHT)
# print(c.__COUNT) #不能访问,因为它属于私有变量,该私有变量python内部做了处理,变量名称被更改。 # print(c.__getweight()) #同上,这是私有方法,该私有方法python内部做了处理,变量名称被更改。 c.showcount1() # c.__showcount2() #无法直接访问父类的私有方法,如果你非要访问的话,可以使用“c._Animal__showcount2()” c.showcount3() print(c._Cat__COUNT) print(c._Animal__COUNT) print(c.NAME) print("{}".format(Animal.__dict__))
print("{}".format(Cat.__dict__))
print(c.__dict__)
print(c.__class__.mro()) #以上代码执行结果如下:
Cat eat
15
<class '__main__.Cat'>
{'__module__': '__main__', 'NAME': 'CAT', '_Cat__COUNT': 200, '__doc__': None}
100
101
200
101
CAT
{'__module__': '__main__', '_Animal__COUNT': 100, 'HEIGHT': 0, '__init__': <function Animal.__init__ at 0x000001F6AB694438>, 'eat': <function Animal.eat at 0x000001F6AB6944C8>, '_Animal__getweight': <function Animal.__getweight at 0x000001F6AB694558>, 'showcount1': <classmethod object at 0x000001F6AB6BA388>, '_Animal__showcount2': <classmethod object at 0x000001F6AB6BA0C8>, 'showcount3': <function Animal.showcount3 at 0x000001F6AB6949D8>, '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None}
{'__module__': '__main__', 'NAME': 'CAT', '_Cat__COUNT': 200, '__doc__': None}
{'_Animal__COUNT': 101, 'age': 3, '_Animal__weight': 5, 'HEIGHT': 15}
[<class '__main__.Cat'>, <class '__main__.Animal'>, <class 'object'>]

2>.总结

  继承时,公有的,子类和实例都可以随意访问;私有成员被隐藏,子类和实例不可直接访问,但私有变量所在的类内的方法中可以访问这个私有变量。

  Python通过自己一套实现,实现和其它语言一样的面向对象的继承机制。

  实例属性查找顺序:
    实例的 "__dict__" ===> "类__dict__" ===> "父类__dict"
    如果搜索这些地方后没有找到就会抛出异常,先找到就立即返回了。

四.方法的重写,覆盖override

1>.super()可以访问到父类的类属性

 #!/usr/bin/env python
#_*_conding:utf-8_*_
#@author :yinzhengjie
#blog:http://www.cnblogs.com/yinzhengjie class Animal:
def shout(self):
print("Animal shouts") class Cat(Animal):
# def shout(self): #覆盖了父类方法
# print("miao") def shout(self): #覆盖了自身的方法,显式调用了父类的方法
print(super())
print(super(Cat,self))
print(super(self.__class__,self)) super().shout()
super(Cat,self).shout() #等价于super()
self.__class__.__base__.shout(self) a = Animal()
a.shout() c = Cat()
c.shout() print(a.__dict__)
print(c.__dict__)
print(Animal.__dict__)
print(Cat.__dict__) #以上代码执行结果如下:
Animal shouts
<super: <class 'Cat'>, <Cat object>>
<super: <class 'Cat'>, <Cat object>>
<super: <class 'Cat'>, <Cat object>>
Animal shouts
Animal shouts
Animal shouts
{}
{}
{'__module__': '__main__', 'shout': <function Animal.shout at 0x000001DD6FB85678>, '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None}
{'__module__': '__main__', 'shout': <function Cat.shout at 0x000001DD6FB851F8>, '__doc__': None}

2>.类方法和静态方法覆盖

 #!/usr/bin/env python
#_*_conding:utf-8_*_
#@author :yinzhengjie
#blog:http://www.cnblogs.com/yinzhengjie """
这些方法都可以覆盖,原理都一样,属性字典的搜索顺序。
""" class Animal: @classmethod
def class_method(cls):
print("class_method_animal") @staticmethod
def static_method():
print("static_method_animal") class Cat(Animal): @classmethod
def class_method(cls):
print("class_method_cat") @staticmethod
def static_method():
print("static_method_cat") c = Cat()
c.class_method()
c.static_method() print(Cat.__dict__)
print(Animal.__dict__) Cat.static_method()
Animal.static_method() #以上代码执行结果如下:
class_method_cat
static_method_cat
{'__module__': '__main__', 'class_method': <classmethod object at 0x0000019B18317588>, 'static_method': <staticmethod object at 0x0000019B183175C8>, '__doc__': None}
{'__module__': '__main__', 'class_method': <classmethod object at 0x0000019B183174C8>, 'static_method': <staticmethod object at 0x0000019B18317508>, '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None}
static_method_cat
static_method_animal

五.继承时使用初始化

1>.手动调用父类的构造方法

 #!/usr/bin/env python
#_*_conding:utf-8_*_
#@author :yinzhengjie
#blog:http://www.cnblogs.com/yinzhengjie class A:
def __init__(self,a,d = 10):
self.a = a
self.__d = d class B:
def __init__(self,b,c):
A.__init__(self,b + c,b - c) #我们调用了A的构造方法,那么就可以使用它的属性啦。
self.b = b
self.c = c def printv(self):
print(self.b)
print(self.a) f = B(200,300)
print(f.__dict__)
print(f.__class__.__bases__)
f.printv() #以上代码执行结果如下:
{'a': 500, '_A__d': -100, 'b': 200, 'c': 300}
(<class 'object'>,)
200
500

2>.自动调用父类的构造方法

 #!/usr/bin/env python
#_*_conding:utf-8_*_
#@author :yinzhengjie
#blog:http://www.cnblogs.com/yinzhengjie class Animal:
def __init__(self,age):
print("init in Animal")
self.age = age def show(self):
print(self.age) class Cat(Animal):
def __init__(self,age,weight):
#调用父类的__init__方法的顺序有时决定着show方法的结果
super().__init__(age)
print("init in Cat")
self.age = age + 1
self.weight = weight
# super().__init__(age) #调用父类的方法其实也可以不用放在第一行,在Java语言中必须放在构造方法的首行。 c = Cat(10,5)
c.show() #以上代码执行结果如下:
init in Animal
init in Cat
11

3>.属性的继承说明

  一个原则,自己的私有属性,就该自己的方法读取和修改,不要借助其它类的方法,即使是父类或者派生类的方法。

六.多继承

1>.Python不同版本的类概述

  Python2.2之前类时没有共同祖先的,之后,引入object类,它时所有类的共同祖先类object。

  Python2.7.X中为了兼容,分为古典类(旧式类)和新式类。

  Python3中全部都是新式类。

  新式类都是继承自object,新式类可以使用super。

2>.Python多继承实现

  在面向对象这种,父类,子类通过继承联系在一起,如果可以通过一套方法,就可以实现不同表现,就是多态。一个类继承自多个类就是多继承,它将具有多个类的特征。

   多继承毕竟会带来路径选择问题,究竟继承哪个父类的特征呢?如上图所示,左图是多继承(菱形继承),右图是单一继承。

  Python使用MRO(method resolution order方法解析顺序)解决基类搜索顺序问题。

  历史原因,MRO有三个搜素算法:
    经典算法,按照定义从左到右,深度优先策略。2.2版本之前左图的MRO是MyClass->D->B->A->C->A
    新式类算法,是经典算法的升级,深度优先,重复的只保留最后一个。2.2版本左图的MRO是MyClass->D->B->C->A->object
    C3算法,在类被创建出来的时候,就计算一个MRO有序列表。2.3之后,Python3唯一支持的算法左图中的MRO是MyClass->D->B->C-A->object的列表。C3算法解决多继承的二义性。
  
  经典算法有很大的问题,如果C中有覆盖A的方法,也不会访问到它,因为先访问A的(深度优先)。   新式类算法,依然采用了深度优先,解决重复问题,但是同经典算法一样,没有解决继承的单调性。
  
  C3算法,解决了继承的单调性,它阻止创建之前产生二义性的代码。求得的MRO本质是为了线性化,且确定了顺序。   单调性:假设有A,B,C三个类,C的mro是[C,A,B],那么C的子类的mro中,A,B的顺序一致就是单调的。

3>.多继承的缺点

  多继承很好的模拟了世界,因为事物很少是单一继承,但是舍弃简单,必然引入复杂性,带来了冲突。

  如同一个孩子继承了来自父母双方的特征。那么到底眼睛像爸爸还是妈妈呢?孩子究竟该像谁多一点呢?

  多继承的实现会导致编译器设计的复杂度增加,所以有些高级编程语言舍弃了类的多继承。

  C++支持多继承;Java舍弃了多继承。

  Java中,一个类可以实现多个接口,一个接口也可以继承多个接口。Java的接口很纯粹,只是方法的声明,继承者必须实现这些方法,就具有了这些能力。就能干什么。

  多继承可能会带来二义性,例如,猫和狗都继承自动物类,现在如果一个多继承了猫和狗类,猫和狗都有shout方法,子类究竟继承谁的shout呢?解决方案:实现多继承的语言,要解决二义性,深度优先或者广度优先。

  当类很多,继承复杂的情况下,继承路径太多,很难说清什么样的继承路径。

  Python语法时允许多继承,但Python代码时解释执行,只是执行到的时候才发现错误。

  团队协作开发,如果引入多继承,那代码很可能不可控。

  不管编程语言是否支持多继承,都应当避免多继承。

  Python的面向对象,我们看到的太灵活了,太开放了,所以要团队守规矩。

七.Mimin类

1>.单继承存在的弊端案例

 #!/usr/bin/env python
#_*_conding:utf-8_*_
#@author :yinzhengjie
#blog:http://www.cnblogs.com/yinzhengjie class Document:
def __init__(self,content):
self.content = content def print(self): #基类中只定义,不实现的方法,称为“抽象方法”。在python中,如果采用这种方式定义的抽象方法,子类可以不实现,知道子类使用该方法的时候才报错。
"""
基类提供的方法可以不具体实现,因为它未必适合子类的打印,子类中需要覆盖重写。
"""
raise NotImplementedError() class Word(Document):
pass class Pdf(Document):
pass """
抛出问题:
从上面的案例可以看出print算是一种能力(打印功能),不是所有的Document的子类都需要的,所以从这个角度触发,上面的基类Document设计有点问题。 解决思路:
如果在现有子类Word或Pdf上直接增加,虽然可以,却违反了OCP的原则(多用“继承”,少修改),所以可以继承后增加打印功能。 在这个时候发现,为了增加一种能力,就要增加一次继承,类可能太多了,继承的方式不是很好了。 功能太多,A类需要某几样功能,B类需要另几样功能,他们需要的是多个功能的自由组合,继承实现很繁琐。 我们可以引入类装饰器来解决问题,装饰器的有点在于:
简单方便,在需要的地方动态增加,直接使用装饰器
可以为类灵活的增加功能。 但是类装饰器不可继承,因此我们引入Mixin方案,Mixin就是其它类混合进来,同时带来了类的属性和方法。 Mixin类本质上就是多继承实现的。Mixin体现的是一种组合的设计模式。
"""

2>.Mixin案例展示

 #!/usr/bin/env python
#_*_conding:utf-8_*_
#@author :yinzhengjie
#blog:http://www.cnblogs.com/yinzhengjie class Document: #假设该类为第三方库,不允许修改
def __init__(self,content):
self.content = content class Word(Document): #假设该类为第三方库,不允许修改
pass class Pdf(Document): #假设该类为第三方库,不允许修改
pass def printable(cls): #定义类装饰器
def _print(self):
print(self.content,"装饰器")
cls.print = _print
return cls @printable
class PrintablePdf(Word): #使用类装饰器和Mixin进行使用上的对比
pass print(PrintablePdf.__dict__)
print(PrintablePdf.mro()) class PrintableMimin: #Mixin就是其它类混合进来,同时带来了类的属性和方法,这里看来和装饰器效果一样,也没有什么特别的。但是Mixin是类,就可以继承。
def print(self):
print(self.content,"Mixin") class PrintableWord(PrintableMimin,Word): #Mixin类本质上就是多继承实现的。Mixin体现的是一种组合的设计模式。
pass print(PrintableWord.__dict__)
print(PrintableWord.mro()) pw = PrintableWord("test string")
pw.print() class SuperPrintableMixin(PrintableMimin):
def print(self):
print("{0} 打印增强前 {0}".format("*" * 20))
super().print()
print("{0} 打印增强后 {0}".format("*" * 20)) class SuperPrintablePdf(SuperPrintableMixin,Pdf):
pass print(SuperPrintablePdf.__dict__)
print(SuperPrintablePdf.mro()) spp = SuperPrintablePdf("super print pdf")
spp.print() #以上代码执行结果如下:
{'__module__': '__main__', '__doc__': None, 'print': <function printable.<locals>._print at 0x0000016E8C6459D8>}
[<class '__main__.PrintablePdf'>, <class '__main__.Word'>, <class '__main__.Document'>, <class 'object'>]
{'__module__': '__main__', '__doc__': None}
[<class '__main__.PrintableWord'>, <class '__main__.PrintableMimin'>, <class '__main__.Word'>, <class '__main__.Document'>, <class 'object'>]
test string Mixin
{'__module__': '__main__', '__doc__': None}
[<class '__main__.SuperPrintablePdf'>, <class '__main__.SuperPrintableMixin'>, <class '__main__.PrintableMimin'>, <class '__main__.Pdf'>, <class '__main__.Document'>, <class 'object'>]
******************** 打印增强前 ********************
super print pdf Mixin
******************** 打印增强后 ********************

3>.Mixin类的使用原则

  在面向对象的设计中,一个复杂的类,往往需要很多功能,而这些功能有来自抓不同的类提供,这就需要很多的类组合在一起。

  从设计模式的角度来说,应该多组合,少继承。

  Mixin类的使用原则:
    Mixin类中不应该显式的出现__init__初始化方法。
    Mixin类通常不能独立工作,因为它式准备混入别的类中的部分功能实现。
    Mixin类的祖先类也应该式Mixin类。   使用时,Mixin类通常在继承列表的第一个位置,例如上来中的"class PrintableWord(PrintableMixin,Word):pass"   Mixin类和装饰器:
    这两种方式都可以使用,看个人喜好。
    如果还需要继承就得使用Mixin类的方式。

Python面向对象三要素-继承(Inheritance)的更多相关文章

  1. Python面向对象三要素-封装(Encapsulation)

    Python面向对象三要素-封装(Encapsulation) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.封装概述 将数据和操作组织到类中,即属性和方法 将数据隐藏起来,给 ...

  2. Python面向对象三要素-多态

    Python面向对象3要素-多态 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.  一.多态概述 OCP原则:多用“继承”,少修改. 继承的用途:在子类上实现对基类的增强,实现多态. ...

  3. Python面向对象中的继承、多态和封装

    Python面向对象中的继承.多态和封装 一.面向对象的三大特性 封装:把很多数据封装到⼀个对象中,把固定功能的代码封装到⼀个代码块, 函数,对象, 打包成模块. 这都属于封装思想. 继承:⼦类可以⾃ ...

  4. Python面向对象(三)

    一.绑定方法与非绑定方法 一.绑定方法:绑定给谁就应该由谁来调用,谁来调用就会将谁当作第一个参数传入 1.绑定给对象的方法:类中定义的函数默认就是绑定给对象的 2.绑定给类的方法:为类中定义的函数加上 ...

  5. Python面向对象之类的继承(2)

    1.除了封装,Python面向对象还有继承这一功能,如下代码,这是简单的继承功能. class Animal: def chi(self): print(self.name+' 吃') def he( ...

  6. Python - 面向对象编程 - 多继承

    继承的详解 https://www.cnblogs.com/poloyy/p/15216652.html 这篇文章讲的都是单继承,Python 中还有多继承 Python 多继承的背景 大部分面向对象 ...

  7. Python()- 面向对象三大特性----继承

    继承: 继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类(基类或超类),新建的类是所继承的类的(派生类或子类) 人类和狗 有相同的属性, 提取了一个__init__方法,在这 ...

  8. python 面向对象八 多继承

    python是支持多继承的,在设计类的继承关系时,通常,主线都是单一继承下来的.但是,如果需要“混入”额外的功能,通过多重继承就可以实现,这种设计通常称之为MixIn. 为了更好地看出继承关系,以Mi ...

  9. python 面向对象(三)--继承和多态

    在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类.父类或超类(Base class.Supe ...

随机推荐

  1. Operation之变换操作符

    buffer buffer方法的作用是缓冲组合, 第一个参数是缓冲时间, 第二个参数是缓冲个数, 第三个参数是线程 该方法简单来说就是缓存Observable中发出的新元素, 当元素达到某个数量, 或 ...

  2. VS2015 调试中断点失效或表达式错误的解决办法

    题描述:在调试前加了断点,但debug时红色的断点变成透明的圆圈加一个感叹号,执行到该处时也不会停止. 这个问题遇到过几次了,前几次都没怎么注意,有时候是因为复制粘贴了某段代码后就这样了,然后点击撤销 ...

  3. 和至少为K的最短子数组

    返回 A 的最短的非空连续子数组的长度,该子数组的和至少为 K 如果没有和至少为 K 的非空子数组,返回 -1 . 示例 1: 输入:A = [1], K = 1 输出:1 示例 2: 输入:A =  ...

  4. ubuntu16.04+cuda8.0+cudnn6.0安装mxnet(极简!+成功!)

    安装MXNet 1.安装 CUDA8.0对应的mxnet版本是mxnet-cu80(同理如果是CUDA9.0对应版本则是mxnet-cu90). 如果pip安装过慢,请参考 Ubuntu16.10下配 ...

  5. web page popup window model

    jQuery UI dialog: https://jqueryui.com/dialog/ Semantic UI https://semantic-ui.com/modules/modal.htm ...

  6. 学习数据结构Day1

    数据结构的分类: 线性结构 数组:栈:队列:链表:哈希表:... 树结构 二叉树:二分查找树:AVL;红黑树:Treap:Splay:堆:栈:Trie:线段树:K-D树:并查集:哈夫曼         ...

  7. HashMap、HashTable与ConcurrentHashMap的区别

    1.HashTable与HashMap (1)HashTable和HashMap都实现了Map接口,但是HashTable的实现是基于Dictionary抽象类. (2)在HashMap中,null可 ...

  8. 推荐一款好用的json导出execl格式的文件的js工具-JsonExportExcel

    <html> <head> <meta charset="utf-8"> <title>json导出Excel</title& ...

  9. IIS提速的几个优化

    一.内存池右键高级设置 1.设置队列5000 2.设置固定回收时间 3.设置空闲时间Suspend 二.网站右键高级设置 1.启用预加载

  10. Kaldi安装

    Kaldi是基于C++开发并遵守Apache License v2.0的一款语音识别工具包,是目前最流行的ASR工具之一,本文基于Ubuntu 18.04 LTS介绍了如何安装Kaldi. 首先按照官 ...