2020-11-18
原文链接: Python入门 class类的继承 - 木头人的文章 - 知乎 https://zhuanlan.zhihu.com/p/30239694


类是面向对象编程的核心, 它扮演相关数据及逻辑的容器角色。它们提供了创建“真实”对象(也就是实例)的蓝图。

  1. # 如何定义类
  2. # 使用 class 关键字定义类。 可以提供一个可选的父类或者说基类; 如果没有合适的基类,
  3. # 那就使用 object 作为基类。class 行之后是可选的文档字符串, 静态成员定义及方法定义。
  4. class FooClass(object):
  5. # my very first class: FooClass
  6. version = 0.1
  7. def __init__(self, nm='John Doe'):
  8. """constructor"""
  9. self.name = nm # class instance (data) attribute
  10. print 'Created a class instance for', nm
  11. def showname(self):
  12. """display instance attribute and class name"""
  13. print 'Your name is', self.name
  14. print 'My name is', self.__class__.__name__
  15. def showver(self):
  16. """display class(static) attribute"""
  17. print self.version # references FooClass.version
  18. def addMe2Me(self, x):
  19. """apply + operation to argument"""
  20. return x + x

Python 实例

  1. foo1 = FooClass() # Created a class instance for John Doe
  2. """
  3. 屏幕上显示的字符串正是自动调用 __init__() 方法的结果。当一个实例被创建,__init__()就会被自动调用。不管这个__int__()是自定义的还是默认的。创建一个类实例就像调用一个函数, 它们确实拥有一样的语法。它们都是可调用对象。类实例使用同样的函数运算符调用一个函数或方法。既然我们成功创建了第一个类实例, 那现在来进行一些方法调用
  4. """
  5. foo1.showname() Your name is John Doe
  6. # My name is __main__.FooClass
  7. foo1.showver() # 0.1
  8. print foo1.addMe2Me(5) # 10

在上面这个类中, 我们定义了一个静态变量 version, 它将被所有实例及四个方法共享,init(), showname(), showver(), 及熟悉的 addMe2Me(). 这些 show() 方法并没有做什么有用的事情, 仅仅输出对应的信息。 init() 方法有一个特殊名字, 所有名字开始和结束都有两个下划线的方法都是特殊方法。 当一个类实例被创建时, init() 方法会自动执行, 在类实例创建完毕后执行, 类似构建函数。init() 可以被当成构建函数, 不过不象其它语言中的构建函数, 它并不创建实例—它仅仅是你的对象创建后执行的第一个方法。它的目的是执行一些该对象的必要的初始化工作。通过创建自己的 init() 方法, 你可以覆盖默认的 init()方法(默认的方法什么也不做),从而能够修饰刚刚创建的对象。在这个例子里, 我们初始化了一个名为 name的类实例属性(或者说成员)。这个变量仅在类实例中存在, 它并不是实际类本身的一部分。 *init()需要一个默认的参数, 前一节中曾经介绍过。毫无疑问,你也注意到每个方法都有的一个参数, self. 什么是 self ? 它是类实例自身的引用。其他语言通常使用一个名为 this 的标识符

Python 类的继承

如下定义一个动物类 Animal 为基类/父类,它基本两个实例属性 name 和 age 、一个方法 call。

  1. class Animal(object): # python3中所有类都可以继承于object 基类
  2. def __init__(self, name, age):
  3. self.name = name
  4. self.age = age
  5. def call(self):
  6. print(self.name, '会叫')
  7. ######
  8. # 现在我们需要定义一个 Cat 猫类继承于 Animal,猫类比动物类多一个 sex 属性。
  9. ######
  10. class Cat(Animal):
  11. def __init__(self,name,age,sex):
  12. super(Cat, self).__init__(name,age) # 不要忘记从 Animal 类引入属性
  13. self.sex=sex
  14. if __name__ == '__main__': # 单模块被引用时下面代码不会受影响,用于调试
  15. c = Cat('喵喵', 2, '男') # Cat继承了父类Animal的属性
  16. c.call() # 输出 喵喵 会叫 ,Cat继承了父类Animal的方法

注意:一定要用 super(Cat, self).init(name,age) 去初始化父类,否则,继承自 Animal的 Cat子类将没有 name和age两个属性。 函数super(Cat, self)将返回当前类继承的父类,即 Animal,然后调用init()方法,注意self参数已在super()中传入,在init()中将隐式传递,不能再写出self。

Python 对子类方法的重构

上面例子中 Animal 的子类 Cat 继承了父类的属性和方法,但是我们猫类 Cat 有自己的叫声 ‘喵喵’ ,这时我们可以对父类的 Call() 方法进行重构。如下:

  1. class Cat(Animal):
  2. def __init__(self, name, age, sex):
  3. super(Cat, self).__init__(name,age)
  4. self.sex = sex
  5. def call(self):
  6. print(self.name,'会“喵喵”叫')
  7. if __name__ == '__main__':
  8. c = Cat('喵喵', 2, '男')
  9. c.call() # 输出:喵喵 会“喵喵”叫

类方法的调用顺序,当我们在子类中重构父类的方法后,Cat子类的实例先会在自己的类 Cat 中查找该方法,当找不到该方法时才会去父类 Animal 中查找对应的方法。

Python 中子类与父类的关系

  1. class Animal(object):
  2. pass
  3. class Cat(Animal):
  4. pass
  5. A= Animal()
  6. C = Cat()

子类与父类的关系是 “is” 的关系,如上 Cat 继承于 Animal 类,我们可以说:

“A”是 Animal 类的实例,但,“A”不是 Cat 类的实例。

“C”是 Animal 类的实例,“C”也是 Cat 类的实例。

判断对象之间的关系,我们可以通过 isinstance (变量,类型) 来进行判断:

  1. print('"A" IS Animal?', isinstance(A, Animal))
  2. print('"A" IS Cat?', isinstance(A, Cat))
  3. print('"C" IS Animal?', isinstance(C, Animal))
  4. print('"C" IS Cat?', isinstance(C, Cat))

输出结果:

  1. "A" IS Animal? True
  2. "A" IS Cat? False
  3. "C" IS Animal? True
  4. "C" IS Cat? True

拓展:isinstance() 判断变量类型

函数 isinstance() 不止可以用在我们自定义的类,也可以判断一个变量的类型,如判断数据类型是否为 int、str、list、dict 等。

  1. print(isinstance(100, int))
  2. print(isinstance('100', int))
  3. print(isinstance(100, str))
  4. print(isinstance('100', str))

输出:

  1. True
  2. False
  3. False
  4. True

python 中多态

类具有继承关系,并且子类类型可以向上转型看做父类类型,如果我们从 Animal 派生出 Cat和 Dog,并都写了一个 call() 方法,如下示例:

  1. class Animal(object):
  2. def __init__(self, name, age):
  3. self.name = name
  4. self.age = age
  5. def call(self):
  6. print(self.name, '会叫')
  7. class Cat(Animal):
  8. def __init__(self, name, age, sex):
  9. super(Cat, self).__init__(name, age)
  10. self.sex = sex
  11. def call(self):
  12. print(self.name, '会“喵喵”叫')
  13. class Dog(Animal):
  14. def __init__(self, name, age, sex):
  15. super(Dog, self).__init__(name, age)
  16. self.sex = sex
  17. def call(self):
  18. print(self.name, '会“汪汪”叫')

我们定义一个 do 函数,接收一个变量 ‘all’,如下:

  1. def do(all):
  2. all.call()
  3. A = Animal('小黑',4)
  4. C = Cat('喵喵', 2, '男')
  5. D = Dog('旺财', 5, '女')
  6. for x in (A,C,D):
  7. do(x)

输出结果:

  1. 小黑 会叫
  2. 喵喵 会“喵喵”叫
  3. 旺财 会“汪汪”叫

小知识:多态

这种行为称为多态。也就是说,方法调用将作用在 all 的实际类型上。C 是 Cat 类型,它实际上拥有自己的 call() 方法以及从 Animal 继承的 call 方法,但调用 C .call() 总是先查找它自身的定义,如果没有定义,则顺着继承链向上查找,直到在某个父类中找到为止。

传递给函数 do(all) 的参数 all 不一定是 Animal 或 Animal 的子类型。任何数据类型的实例都可以,只要它有一个 call() 的方法即可。其他类不继承于 Animal,具备 call 方法也可以使用 do 函数。这就是动态语言,动态语言调用实例方法,不检查类型,只要方法存在,参数正确,就可以调用。

Python 类继承的注意事项

  • 在继承中基类的构造方法(init()方法)不会被自动调用,它需要在其派生类的构造方法中亲自专门调用。
  • 在调用基类的方法时,需要加上基类的类名前缀,且需要带上self参数变量。而在类中调用普通函数时并不需要带上self参数
  • Python 总是首先查找对应类的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找。(先在本类中查找调用的方法,找不到才去基类中找)

类的继承思维导图

Python 面向对象 - 图1