面向对象三(类的定制和继承)2020-11-25

第十三章 面向对象三(类的定制和继承

1. 类的定制和继承

这一关,我们会拓展类的知识疆界,探索两个类的拓展玩法:类的定制和类的继承。说是拓展,是因为这两个知识点属于类中较高阶的操作,让用类写成的代码更容易复用、拓展和维护(这些优势在了解继承和定制后,就能理解。可以这么说:类能成为面向对象编程的主要工具,帮助 Python 在编程世界打下一片疆土,很大程度上是基于它的继承和定制。

1.1 什么是类的继承和定制
1.1.1 类的继承

假设你有个外国朋友,刚来中国。某天,他看到“广东人”这个词,就来问你,你会怎么跟他解释?
可能你会回答:广东是中国的一个地方。广东人就是中国人。他可能会复述:哦!原来就是中国人。
深究的话,“广东人就是中国人”中的“就是”的确切含义是“属于”,即广东人属于中国人。因此,中国人有的属性(如黑头发)和方法(如用筷子),广东人也都有。这么一来,用一句话“广东人是中国人”,便能让他接受多个信息:广东人有黑头发,会用筷子……这个过程相当于:把他脑子里对“中国人”这个类的所有信息都复制了一份,然后放到了“广东人”这个类下面。我们通过事物的归属关系,使信息的传递更为高效。听到“Python是一种计算机语言”,我们就知道Python可以编程;看到“云浮市在广东省”,我们就明白云浮市在中国南方……同样的,编程世界也是如此。我们也可以用一句话,让计算机知道:A类属于B类,自然也拥有了B类的所有属性和方法。这句话在编程里就是:A类继承了B类。

  • 广东人属于中国人——继承了中国人的属性和方法
  • A类属于B类——继承了B类的属性和方法
    在Python中,我们的习惯表述是:A类是B类的子类,而B类是A类的父类
    所以,类的继承,让子类拥有了父类拥有的所有属性和方法。如此,不用白手起家(从头写代码),直接一夜暴富(代码的复用)。
    不过,只有继承的话,子类只是父类的复制而已。那样,为什么不直接用父类,还要增加一个子类?
    要回答这个问题,就需要了解另一个重要的概念:类的定制。
1.1.2 类的定制

还是说回广东人,广东人除了继承中国人的属性方法外,还可以创造【属于自己】的属性或方法,如籍贯开头是广东省(属性)、会说广东话(方法)。甚至,广东人还可以调整继承到的属性或方法,如中国人有个属性“居住的陆地面积(单位:万平方公里)”的值为960,广东人继承后需要将这个属性的值改为17.98。上面的操作,都可以说是广东人在继承的基础上又做了定制。
同样,子类也可以在继承的基础上进行个性化的定制,包括:
(1)创建新属性、新方法;
(2)修改继承到的属性或方法。
用一句话概括就是:以我为主,为我所用。类的定制的前提是继承,而定制的加入让类的继承不仅仅只是单纯的复制。可以回答上面提到的那个问题——为什么我们不直接用父类而创建子类?因为可以定制啊!

1.2 类的继承的语法

在这里插入图片描述
子类继承的属性和方法,也会传递给子类创建的实例。
例题:

class Chinese:
	eye='dark'
	def eat(self):
		print('吃饭用筷子')
class Cantonese(Chinese):
	pass   #表示跳过,不进行任何操作
Yewen = Cantonese()
print(Yewen.eye)
Yewen.eat()

在这里插入图片描述
我们可以看到,通过继承,父类有的属性子类也有。子类创建的实例可以调用父类的属性和方法。
通过一个小括号,子类就能轻轻松松地拥有父类所拥有的一切。不用复制大段大段的代码,只要一个括号,就能复用整块代码。
很多类在创建时也不带括号,如class Chinese:。这意味着它们没有父类吗?
实际上,class Chinese:在运行时相当于class Chinese(object):。而object,是所有类的父类,我们将其称为根类(可理解为类的始祖)。
我们可以用一个函数来验证这一点:函数isinstance(),可以用来判断某个实例是否属于某个类。
具体用法是输入两个参数(第一个是实例,第二个是类或类组成的元组),输出是布尔值(True 或 False)。
例题:

class Chinese:
	eye='dark'
	def eat(self):
		print('吃饭用筷子')
class Cantonese(Chinese):
	pass   #表示跳过,不进行任何操作
Yewen = Cantonese()
Gonger=Chinese()
print(isinstance(Yewen, Chinese))
print(isinstance(Gonger, Chinese))
print(isinstance(Yewen, Cantonese))
print(isinstance(Gonger, Cantonese))
print(isinstance(Yewen, object))
print(isinstance(Gonger, object))

在这里插入图片描述
从上例中我们可以得出实例和类之间的关系:

  • 子类创建的实例同时属于父类
  • 父类创建的实例不属于子类
  • 所有实例都属于根类
    父类可以被无限个子类所继承(这一点好比类的属性方法可以传递给无限个实例)。这个点有什么现实意义吗?举个简单的例子:
    如果要为每个省级行政区的人各创建一个类,并添加各种属性和方法。那么,只要创建一个父类Chinese,在父类中将共同的属性和方法写好,然后34个类都可以通过类的继承得到Chinese的属性和方法,代码量可以减少十几甚至几十倍。
1.3 多层继承和多重继承

除此之外,继承还有两个更有趣的玩法:多层继承和多重继承。让我们见识一下吧。

1.3.1 多层继承

继承不仅可以发生在两个层级之间(即父类-子类),还可以有父类的父类、父类的父类的父类……
如:地球人>亚洲人>中国人>广东人>深圳人…
例题:

class Earthman:
	eyes=2
class Chinese(Earthman):
	eye_color = 'black'
class Cantonese(Chinese):
	pass
yewen = Cantonese()
print(yewen.eyes)
print(yewen.eye_color)

在这里插入图片描述
从上面的例子可得结论:子类创建的实例可调用所有层级父类的属性和方法。
多层继承,属于继承的深度拓展。

1.3.2 多重继承

一个类,可以同时继承多个类,语法为class A(B,C,D):。假设我们将“出生在江苏,定居在广东的人”设为一个类Yuesu,那么,它的创建语句则为class Yuesu(Yue,Su)。class Yuesu(Yue,Su)括号里Yue和Su的顺序是有讲究的。和子类更相关的父类会放在更左侧。
广东人创建的实例在调用属性和方法时,会先在左侧的父类中找,找不到才会去右侧的父类找。(可理解为“就近原则”
例题:

class Su:
	borncity = 'jiangsu'
	wearing = 'thick'
	def diet(self):
		print('爱吃甜')
class Yue:
	settlecity = 'guangdong'
	wearing = 'thin'
	def diet(self):
		print('吃的清淡')
class Yuesu(Yue,Su):
	pass
xiaoming= Yuesu()
print(xiaoming.wearing)
print(xiaoming.borncity)
xiaoming.diet()

大家猜一下会打印出什么结果。
在这里插入图片描述
就近原则:越靠近子类(即越靠左)的父类,越亲近,越优先考虑。子类调用属性和方法时,会先在靠左的父类里找,找不到才往右找。
对比一下这两种有趣的继承方法:
在这里插入图片描述
多层继承和多重继承的结合,让继承的类拥有更多的属性和方法,且能更灵活地调用。进而,继承的力量也得以放大了很多倍。
练习:现在,请你尝试用代码完成下面的继承关系,按照下图类名和属性创建5个类,并打印出C4类的实例的属性name和num。
在这里插入图片描述
答案:

class C0:
	name='C0'
class C1:
	num=1
class C3:
	name='C3'
class C2(C0):
	num=2
class C4(C1,C2,C3):
	pass
d=C4()
print(d.name)
print(d.num)

结果是这样子滴:
在这里插入图片描述
可以发现就近原则中的一个细节:多重继承中,若某父类还有父类的话,会先继续往上找到顶。例如代码中的d.name调用的是C2的父类C0的值而非 C3。也就是先纵向寻找,没有再横向寻找

2. 类的定制

定制可以新增代码,要怎么写呢?

2.1 新增代码

请阅读下面的代码和注释:

class Chinese:
	eye = 'black'
	def eat(self):
		print('吃饭用筷子')
class Cantonese(Chinese):  # 类的继承
	native_place = 'Guangdong' # 类的定制
	def dialect(self):  # 类的定制
		print('我们的方言是广东话')
yewen = Cantonese()
print(yewen.eye)   # 父类的属性能调用
print(yewen.native_place)  # 子类定制的属性也能调用
yewen.eat()   # 父类的方法能调用
yewen.dialect()  # 子类定制的方法也能调用

结果
在这里插入图片描述
可见:我们可以在子类下新建属性或方法,让子类可以用上父类所没有的属性或方法。这种操作,属于定制中的一种:新增代码。

2.2 重写代码

重写代码,是在子类中,对父类代码的修改。
例题:已知中国的面积,广东的面面机为中国的面积的1.88%,那么可以这样写两个类。

class Chinese:
    def land_area(self, area):
        print('我们居住的地方面积%d平方米左右' % area)


class Cantonese(Chinese):
    def land_area(self, area):
        print('我们居住的地方面积%d平方米左右' % int(area * 0.0188))
        # 直接对父类的方法进行重写了。
gonger = Chinese()
yewen = Cantonese()
gonger.land_area(960)
yewen.land_area(960)

结果
在这里插入图片描述
不过,这个其实是不好的示范。虽然目的达成了,但直接重写并不优雅(有点类似洗去了旧方法,然后补上新方法)。
想一想:假设有34个子类需定制这个方法,都是直接重写。那么,假设父类的方法改变,如说法改为“我们脚下的大地的面积有960万平方公里”。那么,就需要将所有子类的代码中的说法也改变。
显然,这样对代码的维护很不友好。所以,下面介绍更优雅的重写方式:

class Chinese:
    def land_area(self, area):
        print('我们居住的地方面积%d平方米左右' % area)
class Cantonese(Chinese):  #间接对方法进行改写
    def land_area(self, area,rate=0.0188):
    	Chinese.land_area(self,area*rate) 
        # 直接继承父类的方法,然后再对参数进行调整。
gonger = Chinese()
yewen = Cantonese()
gonger.land_area(960)
yewen.land_area(960)

结果
在这里插入图片描述
子类继承父类方法的操作是在def语句后接父类.方法(参数),如上述代码的第118、119行。
这样一来,父类方法land_area中的说法改变,子类也不用去动,因为子类直接继承了父类的方法。只不过,在继承的基础上,通过参数的调整完成了定制。
而参数的调整,可以增加参数(如 rate),也可以改变参数的默认值,如下:

class Chinese:
    def land_area(self, area):
        print('我们居住的地方面积%d平方米左右' % area)
class Cantonese(Chinese):  #间接对方法进行改写
    def land_area(self, area=960,rate=0.0188):
    	Chinese.land_area(self,area*rate) 
        # 直接继承父类的方法,然后再对参数进行调整。
yewen = Cantonese()
yewen.land_area()
# 因为两个参数都有默认值,所以可以直接这样调用

在这里插入图片描述
练习:下面,请你通过参数默认值的改变,完成子类的定制,让程序的运行结果为“雷猴!欢迎来到广东。”

 # 提示:初始化方法的定制,和一般的实例方法的定制是一样的。
class Chinese:
    def __init__(self, greeting='你好', place='中国'):
        self.greeting = greeting
        self.place = place

    def greet(self):
        print('%s!欢迎来到%s。' % (self.greeting, self.place))

 # 请为子类完成定制,代码量:两行。
class Cantonese(Chinese):


yewen = Cantonese()
yewen.greet()

答案:

class Chinese:
    def __init__(self, greeting='你好', place='中国'):
        self.greeting = greeting
        self.place = place

    def greet(self):
        print('%s!欢迎来到%s。' % (self.greeting, self.place))

 # 请为子类完成定制,代码量:两行。
class Cantonese(Chinese):
    def __init__(self, greeting='雷猴', palce='广东'):

        Chinese.__init__(self, greeting,palce)


yewen = Cantonese()
yewen.greet()

结果
在这里插入图片描述
这便是定制:在复用代码的基础上,又能满足个性化的需求。类的继承和定制,从某个角度来看,和人类的科技史很像:每一代人,都“继承”了上一代的科技,同时“定制”属于这一代的科技。于是,科技的发展越来越先进。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值