从Scratch到Python 19 面向对象编程

本节我们来介绍“面向对象编程”。

面向对象编程是一种程序设计的思想,也是组织代码结构的一种方法。在我们当前学习的Python程序中,程序是由模块构成的,我们可以在模块里面实现自己的代码逻辑,还可以编写供其它模块调用的自定义函数,基本上我们编写程序的大部分工作就是在调用各种函数。但在面向对象编程中,”对象“是程序的基本单元,对象包含”属性“和”方法“,”属性“就是对象内部的数据,而”方法“就是对象处理数据的函数,而程序执行的过程就是在对象之间传递数据、处理数据的过程。

这个过程似乎有点陌生,让我们温故而知新。

一、Scratch中的”面向对象编程“

实际上,在Scratch编程时,我们一直在实践”面向对象编程“。可能学习Python一段时间后你已经淡忘了这种编程方式。让我们回忆一下,如果要实现一个”贪吃蛇“的程序,我们是怎么做的?

 

 

  • 第一步:插入蛇和食物两个角色,为它们设置好属性,比如造型、大小、位置、角度、旋转方式;
  • 第二步:为蛇角色编写程序让它听从键盘控制,会上下左右移动;
  • 第三步:为食物角色编写程序让它克隆自己,克隆体会隔几秒钟出现在舞台的随机位置;
  • 第四步:在移动时重复检测蛇是否碰到食物或自己的身体,如果碰到食物执行加分程序,蛇身变长;碰到自己身体则游戏结束。

上面的过程是否很熟悉?如果我们把上面的”蛇“与”食物“这两个”角色“理解成”对象“,你是不是就恍然大悟了?原来,我们编程的时候,不就是在和这两个对象打交道吗?以”蛇“这个对象为例:

  • 造型、大小、位置等,是它的属性,我们可以在程序中修改它们,也就是修改对应的数据;
  • 蛇具有向上、向下、向左、向右移动的代码,这就是”方法“,执行这些方法的过程中,它的位置属性也得到更新;
  • 蛇会通过”广播消息“的方式调用自己或其它角色的代码,收到这些消息对应的代码就会执行;
  • 有些消息是系统发送给蛇的,比如收到按钮的消息,蛇会执行相应的代码,这也是它实现的”方法“。

 

 

如果你总结一下,其实我们编写的代码,一定是以某个事件开头的,不管是按下开始按钮,或者按键等,我们处理这些事件的代码都是”蛇“这个对象的”方法“。

为程序中所有的角色——也就是对象,设置好属性,编写它们的方法,这就是我们使用Scratch编程的过程。

这样看来,”面向对象编程“是不是我们的老朋友了?

那么,在Python中,我们是怎么来”面向对象编程“的呢?

二、Python中的面向对象编程

在Python中,要实现”面向对象编程“的方式,你需要先编写”类“。

”类“可以说是生成对象的模板。刚刚说到,在Scratch编程中,我们控制的一个个角色其实就是对象,那这些对象是从哪里来的呢?

是的,可以是角色库中,也可以是我们自己定义的新角色。以小猫卡卡为例,角色库中的小猫卡卡,就是一个类。在我们把它添加到程序中之前,它只是一个抽象的定义,这个定义中包括了默认的属性和方法的实现,但无法被调用:

 

 

现在我们把它添加到程序中,就可以设置它的属性,为它们编写不同的代码了。这里的Cat1、Cat2、Cat3这三个角色就是三个”Cat类“生成的对象,同一种类可以生成许多个对象,它们具有相似的外观和动作,但也有自己独特的属性,比如颜色、大小可以不同:

 

 

事实上,现实生活中我们也是这样把我们遇到的事物进行分类的,比如动物类、植物类、人类……这方便我们识别和思考。以”猫类“为例,它都有类似的外观,都会”喵喵“叫,会爬树……我们可以根据这些特征很快识别出一只猫,然后在遇到具体的猫时,做出不同的反应——比如遇到自己家的猫,你知道它叫”卡卡“,而邻居家那只就叫”小雪“。同样都是人类,有的是你的爸爸妈妈,有的就是你的老师同学……你的生活就是在和许多”类“的”对象“打交道,这可以称为”面向对象生活“?

类似地,在Python编程中,要使用”猫“这个对象,我们会先根据这类对象的属性和方法编写”猫类“的代码,然后基于这个”猫类“生成一个或多个具体的”猫对象“,调用它们的属性和方法实现程序逻辑。也就是说,”类“是抽象的,我们通过类定义好属性和方法,调用的时候要把它”实例化“成一个具体的”对象“再调用它们的方法或修改它们的属性。

要定义一个类,非常简单:

class Cat(object):

  # 这是实例化对象时执行的方法
  def __init__(self, name, color):
    self.name = name
    self.color = color

  def say(self):
    print('miao~,我是', self.name)

这段代码,我们用class关键字定义了一个类Cat,这个类有两个方法,其实也就是类内部的函数: – init():注意这个方法前后是双下划线,它是在实例化时自动执行的方法,我们在实例化Cat这个类时,需要把name、color也就是名字和颜色传递过过去(self代表”自身“,对象的方法都需要这个参数,调用方法的时候Python会自动给self赋值,现在你可以暂时忽略它),它用这两个参数定义了两个同名的属性name和color; – say():这是一个简单的方法,你调用它的时候不需要传递参数,它用在控制台打印”miao~“加自己的名字属性来模拟说话(因为我们还不会写播放声音的Python代码,所以只好这样了)。

有了这个类,怎么使用它呢?

cat1 = Cat('卡卡','yellow')
cat1.say()
cat2 = Cat('小雪','white')
cat2.say()

这段代码中,我们用类名加()的方式创建了Cat类的两个实例,并把它们分别赋值给变量cat1、cat2,调用它们的say()方法,你会看到,作为同一个类的不同对象,它们的表现是不一样的——

 

 

这时,我们就能与原来Scratch的”面向对象“式编程联系起来了。不过你会有疑问,我们为什么要采用这种方法编程?

三、面向对象的特性

简单来说,之所以要采用面向对象编程,首先是因为它符合我们在现实中思考问题的习惯。我们在编程的时候就像导演一样,引入相应的角色,准备好布景(背景,其实也是对象,不过是”背景对象“),安排演员(角色或类)化妆、打扮(也就是设置属性),然后根据我们让演员根据剧本(程序)做相应的表演就可以完成演出。作为导演,你只要想清楚每个对象怎么和其它对象交互就OK了,是不是很方便?

当然,之所以采用面向对象编程,还因为它的一些特性,让它能够适应规模更大、逻辑更复杂的程序编写工作。面向对象具有三个基本特性:

1. 封装:意思是对象把数据封装成”属性“,你不能直接修改这些数据,而是只能通过对象提供的方法来修改它们,这些方法就像是对象提供的对外”接口“,你在使用一个对象时只用关心这些接口而不关心它内部是怎么实现的。比如我们在Scratch中调用方法让小猫移动特定的步数时,并不知道Scratch是怎么让这个图形在舞台上移动的,那些复杂的东西我不关心,我只知道只要调用了方法,它就会按要求移动,这样我就能”用“别人做好的”类“,而不是关心这些”类“是怎么写的,这就有利于类的”复用“,把自己和别人写好的类”组装“成复杂的程序;

2. 继承:继承的意思是类之间可以有类似”父子“的层次关系。比如你定义了一个Cat类,它代表全部的猫,现在你想定义一个更细的类”波斯猫“,那就可以让”波斯猫“类继承Cat,它就自动具有了Cat类的属性和方法,你只要编写波斯猫类特有的属性和方法就可以了,这会在编程中大大节约代码量;

3. 多态:同一个类派生出的子类,它们会有相同的方法,但这些方法执行的逻辑不一样。比如动物类下面的猫类和狗类,都有”叫“的方法,但发声的逻辑不一样。我们即使不知道一个对象是猫还是狗,只要知道它是一个动物类的实例,就可以调用”叫“的方法,这个对象自己知道自己怎么叫——这不是废话吗?然而这个特性在编写一些通用代码时是很有效的。

这三个特性不需要死记硬背,只要知道名字,在以后的编程实践中你会慢慢理解它的用途。

现在我们说说在Python中的具体实现:

封装

如果我们要让对象内部的变量或方法不被外部访问,也就是“封装”起来,对外不可用,可以在变量和方法名字前面加上两个下划线“__”:

class Cat(object):
  def __init__(self, name, color):
    self.name = name
    self.__color = color

  def say(self):
    print('miao~,我是', self.name)


cat1 = Cat('卡卡','yellow')
cat1.say()
print(cat1.__color)

这段代码中,我们给原来定义的”color”属性加上了两个下划线作为前缀,这样它就变成了私有属性,当你在类外面引用它们时,Python会提示你们Cat对象没有“__color”这个属性:

 

继承和多态

看一个继承的例子:

class Cat(object):

  def __init__(self, name, color):
    self.name = name
    self.__color = color

  def say(self):
    print('miao~,我是', self.name)

class BosiCat(Cat):
    def say(self):
        print('miao~,我是波斯猫', self.name)

cat1 = Cat('卡卡','yellow')
cat1.say()
cat2 = BosiCat('小雪','white')
cat2.say()

我们在Cat类的基础上,定义了一个子类“BosiCat”(波斯猫)。这里父类也称为“基类”,子类也称为“派生类”,继承的方式就是在类名后面的括号里写上父类的名字。你会注意到Cat类其实也是有父类的,就是“Object”,也就是通用的对象。我们基于Cat派生出了“BosiCat”之后,它自动具有了父类的属性和方法,同时我们还重写了父类的say()方法,让它在说出自己名字的时候还带上了自己的波斯猫特征。

当我们分别用父类和子类实例化cat1和cat2这两个对象后,再调用它们的同一个方法,输出的结果就会不同,这就是多态:

 

 

多重继承

在实际编程中还会出现一个派生类继承多个基类的情况,不过在我们当前的学习中它不常见,了解一下即可:

class Cat(object):
  def __init__(self, name, color):
    self.name = name
    self.__color = color

  def say(self):
    print('miao~,我是', self.name)

class Pet(object):
    def __str__(self):
        return '我是'+self.name

class BosiCat(Cat,Pet):
    def say(self):
        print('miao~,我是波斯猫', self.name)

cat1 = BosiCat('小雪','white')
print(cat1)

多重继承的写法就是在类后面的括号中写上多个基类的名字并用英文逗号隔开。上面的例子中BosiCat继承了Cat和Pet(宠物)两个类,就会具有这两个类的属性和方法。我们在Pet类中写了str()方法,它和init()一样,前后都有双下划线,代表是特殊的方法,这个方法会在你使用print()函数打印对象时调用,所以在后面的代码中它就输出了这个方法返回的字符串内容。如果不写str()方法,调用print()函数打印对象时会输出什么呢?你可以自己试一下。

 

 

四、课后作业

目前我们已经了解了面向对象编程的基本概念和在Python中写出面向对象程序的基本方法,现在请你编写一个银行账户类Card,这个类具有编号 card_no、姓名name 和金额 balance 三个属性,并实现三个方法:

  • init():类实例化,需要卡号参数和余额参数;
  • save():存款,需要一个金额参数,当存入特定金额时,要在原有余额基础上加上存入金额;
  • str():返回编号、姓名和当前的余额。

实例化两个账户并进行操作:

  • 编号001,姓名小王,余额2000;
  • 编号002,姓名小李,余额4000;
  • 小李存入6000元;
  • 小王存入3000元;
  • 使用print()函数输出两个对象的信息。

给TA赞助
共{{data.count}}人
人已赞助
综合资讯

从Scratch到Python 18 数学计算

2023-6-6 11:26:57

综合资讯

从Scratch到Python 20 用好现成的“轮子”

2023-6-6 11:29:47

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索