7.8 类的继承

    时代在变迁,消费品的种类在不断增长,现在的时代早已经不是 Andy Warhol 那个只有一个口味的可口可乐的时代了,而且也并非是所有可口可乐的味道一样好——如果喝过樱桃味的可乐你一定会明白。可口可乐本身的口味也是根据现代人的需求变了又变。

    7.8 类的继承 - 图1

    现在我们使用可口可乐官方网站上最新的配方来重新定义这个类:

    class CocaCola:
    calories = 140
    sodium = 45
    totalcarb = 39
    caffeine = 34
    ingredients = [
    'High Fructose Corn Syrup',
    'Carbonated Water',
    'Phosphoric Acid',
    'Natural Flavors',
    'Caramel Color',
    'Caffeine'
    ]

    def _init
    (self,logo_name):
    self.local_logo = logo_name

    def drink(self):
    print('You got {} cal energy!'.format(self.calories))

    不同的本地化策略和新的种类的开发,使得生产并非仅仅是换个标签这么简单了,包装、容积、甚至是配方都会发生变化,但唯一不变的是:它们永远是可口可乐。

    所有的子品类都会继承可口可乐的品牌,Python 中类自然也有对应的概念,叫做类的继承 (inheritance),我们拿无咖可乐(CAFFEINE-FREE)作为例子:

    class CaffeineFree(CocaCola):
    caffeine = 0
    ingredients = [
    'High Fructose Corn Syrup',
    'Carbonated Water',
    'Phosphoric Acid',
    'Natural Flavors',
    'Caramel Color',
    ]

    coke_a = CaffeineFree('Cocacola-FREE')

    coke_a.drink()

    我们在新的类 CaffeineFree 后面的括号中放入 CocaCola,这就表示这个类是继承于 CocaCola 这个父类的,而 CaffeineFree 则成为了 CocaCola 子类。类中的变量和方法可以完全被子类继承,但如需有特殊的改动也可以进行覆盖(override)。

    可以看到CAFFEINE-FREE存在着咖啡因含量、成分这两处不同的地方,并且在新的类中也仅仅是重写了这两个地方,其他没有重写的地方,方法和属性都能照常使用。

    令人困惑的类属性与实例属性

    Q1:类属性如果被重新赋值,是否会影响到类属性的引用?
    class TestA:
    attr = 1
    obj_a = TestA()

    TestA.attr = 42
    print(obj_a.attr)
    Q2:实例属性如果被重新赋值,是否会影响到类属性的引用?
    class TestA:
    attr = 1
    obj_a = TestA()
    obj_b = TestA()

    obj_a.attr = 42

    print(obj_b.attr)
    Q3:类属性实例属性具有相同的名称,那么 . 后面引用的将会是什么?
    class TestA:
    attr = 1
    def init(self):
    self.attr = 42

    obj_a = TestA()

    print(obj_b.attr)

    也许运行完上面三段代码,你会有一些初步的结论,但是更为直接的解释,全部隐藏在类的特殊属性 dict 中。dict 是一个类的特殊属性,它是一个字典,用于储存类或者实例的属性。即使你不去定义它,它也会存在于每一个类中,是默认隐藏的。我们以问题3中的代码为背景,在下面添加上这两行:

    print(TestA.dict)
    print(obja._dict)

    我们可以看到这样的结果:

    {'module': 'main', 'doc': None, 'dict': <attribute 'dict' of 'TestA' objects>, 'init': <function TestA.init at 0x1007fc7b8>, 'attr': 1, 'weakref': <attribute 'weakref' of 'TestA' objects>}

    {'attr': 42}

    其中类 TestA 和类的实例 obj_a 拥有各自的 attr 属性,井水不犯河水,是完全独立的。但是这样一来又如何解释问题1中的情况呢?为什么引用实例属性的方式会和引用类属性的方式是一样的呢?为何在问题3中返回的是42而不是1呢?看下面的图:

    如图所示, Python 中属性的引用机制是自外而内的,当你创建了一个实例之后,准备开始引用属性,这时候编译器会先搜索该实例是否拥有该属性,如果有,则引用;如果没有,将搜索这个实例所属的类是否有这个属性,如果有,则引用,没有那就只能报错了。

    7.8 类的继承 - 图2

    类的扩展理解

    现在试着敲下这几行代码:

    obj1 = 1
    obj2 = 'String!'
    obj3 = []
    obj4 = {}

    print(type(obj1),type(obj2),type(obj3),type(obj4))

    Python 中任何种类的对象都是类的实例,上面的这些类型被称作内建类型,它们并不需要像我们上面一样实例化。

    如果你安装了 Beautifulsoup4 这个第三方的库,你可以试着这样:

    from bs4 import Beautifulsoup
    soup = BeautifulSoup
    print(type(soup))

    然后你可以按住 cmd (win系统为ctr)点击 Beautifulsoup 来查看一个soup 对象的完整类定义。

    到了这里你已经掌握类的基础用法,现在我们还不想把事情搞得复杂,至少现在还不值得浪费更多时间去深入你暂时不会使用到的高级概念。要记住,不是越多就越好,你不可能在短时间内掌握诸多交织密集的抽象概念。(一些关于类的高级概念你会在后面的实战项目中学到)

    你不用担心如何验证自己是否掌握类的概念,接下来将带你做一个关于类的实践,到那时你可以验证自己的理解是否到位。