博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
第10章 面向对象编程
阅读量:2193 次
发布时间:2019-05-02

本文共 11324 字,大约阅读时间需要 37 分钟。

        本章简要地介绍面向对象编程(简称OOP)。OOP是一种组织程序的方法,提倡仔细设计和代码重用。大多数现代编程语言都支持OOP,事实证明这是一种组织和创建大型程序的实用方式。

        从本质上说,对象是一组数据以及操作这些数据的函数。本书一直在使用 Python对象,因为数字、字符串、列表、字典和函数都是对象。

        要创建新型对象,必须先创建类。从本质上说,类就是设计蓝图,用于创建特定类型的对象。类指定了对象将包含哪些数据和函数,还指定了对象与其他类的关系。对象封装了数据以及操作这些数据的函数。

        一个重要的OOP功能是继承:创建新类时,可让它继承既有类的数据和函数。妥善地使用继承可避免重机关报编写是代码,还可让程序更容易理解。

10.1 编写类

下面就来介绍OOP——编写一个表示人的简单类:

 

#person.pyclass Person:    """Class to represent ap person    """    def __init__(self):        self.name = ''        self.age = 0

        上述代码定义了一个名为Person的类。它定义了Person对象包含的数据和函数。Person类很简单,它包含数据name和age当前唯一一个函数是__init__,这是用于初始化对象值 的标准函数。正如你将看到的,你创建Person对象时,Python将自动调用__init__.

术语说明

        在有些OOP语言中,__init__被称为构造函数,因为它构造对象。每次创建新对象时,都将调用构造函数。在Java和C++等语言中,创建对象时需要使用关键字new。

        在类中定义的函数称为方法。与__init__一样,方法的第一个参数必须是self.

可能像下面这样使用Person对象:

        要创建Person对象,只需调用Person()。这导致Python运行Person类的函数__init__,并返回一个新的Person对象。

        变量age和name包含在对象中,因此每个新创建的Person对象都有自己的age和name。要访问age和name,必须使用句点表示法指定存储它们的对象。

参数self

        你可能注意到了,我们调用Person()时没有提供任何参数,但函数__init__(self)期望获得名为self的输入。这是因为在OOP中,self是一个指向对象本身的变量,(相当于C++中的this.)

如图10-1所示。

10.2 显示对象

        前面说过,方法是在类中定义的函数。下面给Person类添加一个方法,用于打印Person对象的内容 :

 

#person2.pyclass Person:    """Class to represent a person    """    def __init__(self):        self.name = ''        self.age = 0    def display(self):        print("Person('%s', %d)" % (self.name, self.age))

方法display将Person对象的内容以适合程序员阅读的格式打印到屏幕上:

方法dispaly的效果很好,但我们还可以做得更好:Python提供了一些特殊方法,让你能够定制对象以支持天衣无缝的打印。例如,特殊方法__str__用于生成对象的字符串表示:

 

#person3.pyclass Person:    """Class to represent a person    """    def __init__(self):        self.name = ''        self.age = 0    def display(self):        print("Person('%s', %d)" % (self.name, self.age))    def __str__(self):        return "Person('%s', %d)" % (self.name, self.age)

现在我们可以这样编写代码:

还可使用str来简化方法display:

 

#person4.pyclass Person:    """Class to represent a person    """    def __init__(self):        self.name = ''        self.age = 0    def display(self):        print(str(self))    def __str__(self):        return "Person('%s', %d)" % (self.name, self.age)

运行:

        你还可以定义特殊方法__repr__,它返回对象的“官方”(official)表示。例如,Person对象的默认官方表示不太实用:

通过添加方法__repr__,我们可控制这里打印的字符串。在大多数类中,方法__repr__都与方法__str__相同:

 

#person5.pyclass Person:    """Class to represent a person    """    def __init__(self):        self.name = ''        self.age = 0    def display(self):        print(str(self))    def __str__(self):        return "Person('%s', %d)" % (self.name, self.age)    def __repr__(self):        return str(self)

现在Person对象使用起来更容易:

10.3 灵活的初始化

当前,要创建具有特定姓名和年龄的Person对象,必须这样做:

一种更方便的方法是,在构造对象时将姓名和年龄传递给__init__。为此,需要重写__init__:

 

#person6.pyclass Person:    """Class to represent a person    """    def __init__(self, name = '', age = 0):        self.name = name        self.age = age    def display(self):        print(str(self))    def __str__(self):        return "Person('%s', %d)" % (self.name, self.age)    def __repr__(self):        return str(self)

这样,初始化Person对象将简单得多:

由于__init__的参数有默认值 ,你甚至可以创建“空的”Person对象:

        注意方法__init__,我们在其中使用了self.name和name(以及self.name和age)。变量name指向传入__init__的值,而self.name指向存储在对象中的值。使用self更清楚地指出了谁是谁。

10.4 设置函数和获取函数

当前,我们可以使用句点表示法来读写Person对象的name和age值:

        这种做法存在的一个问题是,可能不小心将年龄设置为荒谬的值,如-45, 509。对于常规Python变量,无法对可赋给它的值进行限制。但在对象中,可编写特殊的设置函数(setter)和获取函数(getter),对存取值的方式进行控制。

首先,添加一个设置函数,它公在提供的值合理时才修改age:

 

#person7.pyclass Person:    """Class to represent a person    """    def __init__(self, name = '', age = 0):        self.name = name        self.age = age    def display(self):        print(str(self))    def __str__(self):        return "Person('%s', %d)" % (self.name, self.age)    def __repr__(self):        return str(self)    def set_age(self, age):        if 0 < age <= 150:            self.age = age

运行:

        对于这种设置函数,一种常见的抱怨是,输入p.set_age(30)比输入p.age=30更烦琐。为解决这种问题,可使用特性装饰器(property decorator)

10.4.1 特性装饰器

特性装饰器融变量的简单与函数的灵活于一身。装饰器指出函数或方法有点特殊,这里使用它们来指出函数或方法有点特殊,这里使用它们来指示设置函数和获取函数。

获取函数返回变量的值,我们将使用@property装饿饰器来指出这一点:

@property

    def age(self):

        """Returns this person's age.

        """

        return self._age

        这个age方法除必不可少的self外不接受任何参数。我们在它前面加上了@property,指出这里一个获取函数。这个方法的名称将被用于设置变量。

        我们还将底层变量self.age重命名为self._age。在对象变量前加上下划线是一种常见的做法,这里使用这种方式 将这个变量与方法age区分开来。你需要将Person类中的每个self.age换成self._age。为保持 一致性,最后也将self.name换成self._name。修改后的Person类类似于下面这样:

 

#person8.pyclass Person:    """Class to represent a person    """    def __init__(self, name = '', age = 0):        self._name = name        self._age = age    def display(self):        print(str(self))    def __str__(self):        return "Person('%s', %d)" % (self._name, self._age)    def __repr__(self):        return str(self)    @property    def age(self):        return self._age    def set_age(self, age):        if 0 < age <= 150:            self._age = age

运行结果:

为给age创建设置函数,我们将方法set_age重命名为age,并使用@age.setter进行装饰:

 

#person9.pyclass Person:    """Class to represent a person    """    def __init__(self, name = '', age = 0):        self._name = name        self._age = age    def display(self):        print(str(self))    def __str__(self):        return "Person('%s', %d)" % (self._name, self._age)    def __repr__(self):        return str(self)    @property    def age(self):        return self._age    @age.setter    def age(self, age):     #设置函数必须在获取函数之后定义        if 0 < age <= 150:            self._age = age

完成这些修改后,运行:

        由于给age提供了设置函数和获取函数,编写的代码就像直接使用变量age,但差别在于:遇到代码p.age = 230时,Python实际上将调用方法age(self, age);同样,遇到代码p.age时,将调用方法age(self)。这提供了如下优点:赋值语法很简单,同时可控制变量的设置和获取方法。

10.4.2 私有变量

依然可以直接访问self._age:

>>>p._age = -44

>>>p

Person('Lia', 44)

        问题在于,直接修改_age可能导致对象不一致,因此通常不希望直接修改的情况发生。

        为降低变量self._age被直接修改的可能性,一种方式是将其重命名为self.__age,即在变量名开头包含两个下划线。两个下划线表明age是私有变量,不应在Person类外直接访问它。

 

#person10.pyclass Person:    """Class to represent a person    """    def __init__(self, name = '', age = 0):        self.__name = name        self.__age = age  #不以下划线打头的变量是公有变量,任何代码都可访问它们    def display(self):        print(str(self))    def __str__(self):        return "Person('%s', %d)" % (self.__name, self.__age)    def __repr__(self):        return str(self)       @property    def age(self):        return self.__age    @age.setter    def age(self, age):        if 0 < age <= 150:            self.__age = age

 修改之前:

修改之后运行结果:

要直接访问self.__age,需要在前面中上_Person,如下所示:

这虽然不能禁止你直接修改内部变量,但将无意间这样做的可能性几乎降到了零。

提示

编写大型程序时,一条实用 的经验规则是,首先将所有对象变量都设置为私有的(即以两个下划线打头),再在有充分理由的情况下将其改为公有的。这可避免无意间修改对象内部变量导致的错误

10.5 继承

        继承是一种重用类的机制,让你能够这样创建全新的类:给既有类的考贝添加变量和方法。

        假设我们要开发一款游戏,其中涉及人类玩家和计算机玩家。为此,可创建一个Player类,它包含玩家都有的东西,如得分和名称:

 

#players.pyclass Player:    def __init__(self, name):        self._name = name        self._score = 0    def reset_score(self):        self._score = 0    def incr_score(self):        self._score = self._score + 1    def get_name(self):        return self._name    def __str__(self):        return "name = '%s', score = %s" % (self._name, self._score)    def __repr__(self):        return 'Player(%s)' % str(self)

运行结果:

 

        咱们假设有两类玩家:人和计算机。主要差别在于,人通过键盘输入走法,而计算机使用函数生成走法,除此之外,这两类玩家相同,它们都有名称和得分。

        下面来编写一个Human类,用于表示人类玩家。为此,一种办法是通过复制并粘贴新建Player类的一个拷贝,再添加让玩家走棋的方法make_move(self)。这种办法虽然可么,但更佳的做法是使用继承。我们可以让Human类继承Player类的所有变量和方法, 样就不需要再次编写它们了。

#human.pyclass Player:    def __init__(self, name):        self._name = name        self._score = 0    def reset_score(self):        self._score = 0    def incr_score(self):        self._score = self._score + 1    def get_name(self):        return self._name    def __str__(self):        return "name = '%s', score = %s" % (self._name, self._score)    def __repr__(self):        return 'Player(%s)' % str(self)​class Human(Player):    #继承Player类    pass

在Python中,pass语句表示“什么都不做”。对Human类来说,这是一个完整而实用的定义,它继承Player的代码,让我们能够像下面这样做:

考虑到我们只为Human类编写两行代码,这相当令人难忘

重写方法

一个小瑕疵是,h的字符串表示为Player,但更准确的说法应该是Human。为修复这种问题,可给Human定义方法__repr__:

 

#human2.pyclass Player:    def __init__(self, name):        self._name = name        self._score = 0    def reset_score(self):        self._score = 0    def incr_score(self):        self._score = self._score + 1    def get_name(self):        return self._name    def __str__(self):        return "name = '%s', score = %s" % (self._name, self._score)    def __repr__(self):        return 'Player(%s)' % str(self)​class Human(Player):    #继承Player类    def __repr__(self):     #定制派生类的__repr__        return 'Human(%s)' % str(self)

        

        这就是方法重写:Human中的方法__repr__重写了从Player那里继承的方法__repr__.这是定制派生类的常用方式。

        现在,可轻松编写类似的Computer类,用于表示计算机玩家:

class Computer(Player):

    def __repr__(self):

        return 'Computer(%s)' % str(self)

 

#computer.pyclass Player:    def __init__(self, name):        self._name = name        self._score = 0    def reset_score(self):        self._score = 0    def incr_score(self):        self._score = self._score + 1    def get_name(self):        return self._name    def __str__(self):        return "name = '%s', score = %s" % (self._name, self._score)    def __repr__(self):        return 'Player(%s)' % str(self)class Human(Player):    #继承Player类    def __repr__(self): #定制派生类的__repr__        return 'Human(%s)' % str(self)   class Computer(Player):    #继承Player类    def __repr__(self): #定制派生类的__repr__        return 'Computer(%s)' % str(self)

运行结果:

这3个类组成了一个小型的类层次结构,如图10-2的类图所示。Player为基类,而其他两个类为派生(扩展)类。

10.6 多态

        为演示OOP的威力,咱们来创建一个名为Undercut的简单游戏。在这个游戏中,两个玩家同时选择一个1——10的整数,如果一个玩家选择的整数比对方选择整数的小1,则该玩家获胜,否则算打平。例如,如果Thomas和Bonnie一起玩游戏Undercut,且他们选择的数字分别为9和10,则Thomas获胜;如果他们分别选择4和7,则打成平手。

 

#polymorphous.pyimport randomclass Player:    def __init__(self, name):        self._name = name        self._score = 0    def reset_score(self):        self._score = 0    def incr_score(self):        self._score = self._score + 1    def get_name(self):        return self._name    def __str__(self):        return "name = '%s', score = %s" % (self._name, self._score)    def __repr__(self):        return 'Player(%s)' % str(self)​class Human(Player):    #继承Player类    def __repr__(self): #定制派生类的__repr__        return 'Human(%s)' % str(self)    def get_move(self):        while True:            try:                n = int(input('%s move (1-10: )' % self.get_name()))                if 1 <= n <= 10:                    return n                else:                    print('Oops!')            except:                print('except-----Oops!')class Computer(Player):    #继承Player类    def __repr__(self): #定制派生类的__repr__        return 'Computer(%s)' % str(self)    def get_move(self):        return random.randint(1, 10)​def play_undercut(p1, p2):    p1.reset_score()    p2.reset_score()    m1 = p1.get_move()    m2 = p2.get_move()    print("%s move: %s" % (p1.get_name(), m1))    print("%s move: %s" % (p2.get_name(), m2))    if m1 == m2 - 1:        p1.incr_score()        return p1, p2, '%s wins!' % p1.get_name()    elif m2 == m1 - 1:        p2.incr_score()        return p1, p2, '%s wins!' % p2.get_name()    else:        return p1, p2, 'draw: no winner'

10.6.2 玩游戏Undercut

万事俱备,可以开始玩游戏Undercut了。先让人和计算机来玩款游戏:

        必须在函数play_undercut外面创建玩家对象,明白这一点很重要。这是一种良好的设计:函数play_undercut只关心玩游戏,而不关心如何初始化玩家对象。

函数play_undercut返回一个形式为(p1,  p2, message)的三元组。其中p1和p2是传入的玩家对象;如果有玩家获胜,则将其得分加1。message是一个字符串,指出了获胜的玩家或打成平手。

也可以将两个计算机玩家传递给函数play_undercut:

这次没有人类玩家,因此不会要求用户输入数字。

还可以传入两个人类玩家:

10.7 更深入地学习

        本章介绍了OOP的一些基本知识,Python还有很多其他OOP功能,你可通过阅读在线文档进行学习。

        一个重要主题是打造良好的面向对象设计。相比于仅仅使用对象,妥善地使用对象要难得多。为组织面向对象的程序,一种流行的方式是使用面向对象设计模式。面向对象设计模式是使用对象解决常见编程问题的处方,经过了实践的检验。

在探计这个主题的著作中,Erich Gamma, Richard Helm, Ralph Johnson和John Vlissides所著的《设计模式:可复用面向对象软件的基础》影响最为深元。掌握OOP的所有技术细节后,如果要了解重要的设计问题,阅读这部著作将是很不错的选择。

转载地址:http://yyiub.baihongyu.com/

你可能感兴趣的文章
【LEETCODE】21-Merge Two Sorted Lists
查看>>
【LEETCODE】231-Power of Two
查看>>
【LEETCODE】172-Factorial Trailing Zeroes
查看>>
【LEETCODE】112-Path Sum
查看>>
【LEETCODE】9-Palindrome Number
查看>>
【极客学院】-python学习笔记-Python快速入门(面向对象-引入外部文件-Web2Py创建网站)
查看>>
【LEETCODE】190-Reverse Bits
查看>>
【LEETCODE】67-Add Binary
查看>>
【LEETCODE】7-Reverse Integer
查看>>
【LEETCODE】165-Compare Version Numbers
查看>>
【LEETCODE】299-Bulls and Cows
查看>>
【LEETCODE】223-Rectangle Area
查看>>
【LEETCODE】12-Integer to Roman
查看>>
【学习方法】如何分析源代码
查看>>
【LEETCODE】61- Rotate List [Python]
查看>>
【LEETCODE】82- Remove Duplicates from Sorted List II [Python]
查看>>
【LEETCODE】86- Partition List [Python]
查看>>
【LEETCODE】147- Insertion Sort List [Python]
查看>>
【算法】- 动态规划的编织艺术
查看>>
用 TensorFlow 让你的机器人唱首原创给你听
查看>>