Skip to content

Latest commit

 

History

History
272 lines (198 loc) · 7.9 KB

object_oriented3.md

File metadata and controls

272 lines (198 loc) · 7.9 KB

一切皆对象——Python面向对象(三)

Python单继承

Python中继承的语法是通过类名后的括号内增加父类名实现的:

class Father:
	pass


class Son(Father):
    # 继承于Father
    pass

当然,子类会直接继承父类的所有属性和方法:

Father.print_func = print
# 动态绑定方法
s = Son()
s.print_func('hi') # hi

子类也能够重写父类的方法:

class Father:
    def print_func(self, content):
        print(content)

        
class Son(Father):
    def print_func(self, content):
        print('hi')

f = Father()
s = Son()
f.print_func('hello') # hello
s.print_func('hello') # hi

查看子类的所有父类,可以直接读取__bases__属性:

print(Son.__bases__)
# (<class '__main__.Father'>,)

现在来看一个问题:

class Father:
    def __init__(self, age):
        self.age = age
  

class Son(Father):
    def __init__(self, height):
        self.height = height
        
s = Son(100)
print(s.age) 
# AttributeError: 'Son' object has no attribute 'age'

很明显,Son覆盖了Father的初始化函数,age没有初始化,自然无法访问。那在Son里要怎么给Father初始化呢?很简单,直接调用Father__init__方法:

class Son(Father):
    def __init__(self, height, age):
        Father.__init__(self, age)
        self.height = height
        
s = Son(height=100, age=10)
print(s.age) # 10

这又带来了另一个问题,如果一个Father有一百个子类,突然有一天,Father类改名叫Farther了,那么所有的子类都要修改初始化处的名字。更一般的,如果子类使用了大量的父类的方法,每个方法都要去修改Father这个名字,着实有些困难。所以,Python提供了一个更好的选择,利用super

class Son(Father):
    def __init__(self, height, age):
        super(Son, self).__init__(age=age)
        
s = Son(height=100, age=10)
print(s.age) # 10

在Python 3中,super不必加任何参数,直接调用super().__init__即可。在后续多重继承文章中会详细解释super本身及参数的意义。在单继承中,只要知道,利用super可以调用到父类的方法即可:

class Father:
    def print_func(self):
        print('I am father')
 

class Son(Father):
    def print_func(self):
        super().print_func()
        print('I am son')
        
s = Son()
s.print_func()
# I am father
# I am son

但是切记,super的意义并不是用于调用父类方法super也存在一些问题。所以,当你能很明确地确定继承结构,并且很明确地确定继承结构基本不变,你应当直接用父类名调用父类方法,除非你很明确地清楚你在用super作什么

继续来通过例子看Python中类的问题:

class Father:
    def print_age(self):
        print('My age: {}'.format(self.age))
  

class Son(Father):
    def __init__(self, age):
        self.age = age

f = Father()
f.print_age()
# ???
s = Son(10)
s.print_age()
# ???

上述两个调用的结果是怎样的?

# AttributeError: 'Father' object has no attribute 'age'
# My age: 10

很奇怪,父类的方法为什么能直接打出子类的属性??

答案就在于self。我们在一个更复杂的例子中看一下:

class Father:
    def print_age(self):
        print('My age: {}'.format(self.age))

        
class Mother:
    pass
   
    
class Uncle:
    def print_age(self):
        print('Uncle\'s age: {}'.format(self.age))

        
class Son(Mother, Father, Uncle):
    def __init__(self, age):
        self.age = age

s = Son(10)
s.print_age()
# My age: 10

可以看到,上例中Son继承自三个父类(多重继承在后续文章中详细介绍)。print_age仍旧找到了Father类中。它的内部调用情况是这样的:

for base_class in Son.__bases__:
    print(base_class)
# 这里打印只是为了方便查看
    if hasattr(
        base_class,
        'print_age'
    ):
        base_class.print_age(s)
        break
# <class '__main__.Mother'>
# <class '__main__.Father'>
# My age: 10

Python先在Son里查找方法print_age,没有找到,之后便在Son的所有父类中依次寻找。在Mother类中什么都没找到,继续在Father类中寻找。hasattr方法可以判断一个对象中是否有某个方法。在Father中找到了print_age,然后以Son的实例s作为参数调用Fatherprint_age,这样Father.print_age的参数self则变成了s,所以,打印self.age即是打印s.age

一旦找到了并完成调用后,即break掉该循环,不再从后续父类中再做查找。

如果你熟悉C++的面向对象编程,你应该发现Python类的方法都是虚函数(virtual),因为你可以从父类通过self访问到子类的方法。

class Father:
    def print_name(self):
        print('Father')
    def who(self):
        self.print_name()
        

class Son(Father):
    def print_name(self):
        print('Son')
        
s = Son()
s.who()
# 结果是什么?

不再解释。当然Python中并不存在虚函数这种说法,仅仅是做一种类比。也希望大家在学习使用Python的时候尽量以Python的思路来思考,而不要以其他语言的思路来揣测Python的行为。

下面来看一下Son对象的类型:

# 1
print(isinstance(s, Son)) # True
# 2
print(isinstance(s, Father)) # True 
# 3
print(isinstance(s, type)) # False
# 4
print(isinstance(s, object)) # True
# 5
print(isinstance(Son, type)) # True
# 6
print(isinstance(Son, object)) # True
# 7
print(issubclass(Son, type)) # False
# 8
print(issubclass(Son, object)) # True
# 9
print(isinstance(Father, type)) # True
# 10
print(type(Father)) # <class 'type'>
# 11
print(Father.__bases__) 
# (<class 'object'>,)

在这篇文章中解释了(注:issubclass示例有误),isinstance(a, b)查询a是否是b实例(对象),issubclass(a, b)查询a是否是b子类type(a)返回a的类型

我们按顺序一个个解释一下:

  1. sSon的实例,isinstance(s, Son)自然是True
  2. SonFather的子类,所以Son的实例s自然也是Father的实例,大家都是同一血缘的;
  3. 如果你仔细阅读了一切皆对象——Python面向对象(二),你应该清楚type是一切的类(或者叫类型),而不是一切实例的类!所以实例s并不是type的实例!类Son和类Father才是type的实例!请看# 5# 9
  4. object是什么?之前说过,Python一切皆对象,任何东西都能找到它的类型。而任何类型的最终源头是type。实际上,任何的类型都是从一个父类(或者叫基类)继承过来的,而父类的最终源头便是object。请看# 8# 11
  5. 在3解释过了;
  6. object一切类最终的基类,一切自然也包括type
print(type.__bases__)
# (<class 'object'>,)

object再无基类:

print(object.__bases__)
# ()

既然objecttype的父类,而Sontype的实例,那么Son自然是object的实例,类比于s即时Son的实例也是Son的父类Father的实例;

  1. type不是父类,而是类型!object才是父类!

  2. 在4解释过了;

  3. 在3解释过了;

  4. 在系列的上一篇中解释了;

  5. 在4解释过了;