Python 面向对象编程

面向对象程序设计

结构化程序设计的缺点

我们为什么要有面向对象程序设计呢?我们使用 C 语言只能实现结构化程序设计,所谓结构化程序设计就是“程序 = 数据结构 + 算法”,而在程序中会有很多可以相互调用的函数和全局变量。

但是我们可以显然地看出,这种编程风格存在不少缺点。首先由于函数之间可以相互调用,任何函数都可以修改全局变量,这就导致了函数、数据结构之间的关系一段乱麻,尤其是当代码量很长的时候,代码的理解也变得极其困难。

  • 这个函数是用来操作哪个还是哪些数据数据结构?
  • 这个数据结构可以被哪些函数操作,代表什么含义?
  • 不同的函数能够相互调用吗?关系是什么?

对于变量来说,有的变量可能是只能被一些函数修改,而不能被某些函数操作,但是由于你不能给变量“上锁”,因此这个变量会很轻松地被修改。当然你可以说可以搞成常量,那我如果要修改呢?搞成常引用?算了吧,这样变量间的关系就跟复杂了。而且,某个数据结构会被多个函数调用,而如果出现了错误,是哪个函数出错了呢?是函数一、函数二、函数 N,还是都有错?除了调试好像还真没啥好办法。
作为一个懒人,如果我的一个程序的某个功能我曾经写过,那么我就很喜欢去把以前的代码搞来用。可是往往这会是一件困难的事情,因为这段代码的源程序的变量和函数间本身就有复杂的逻辑关系,接口可能完全不一样,最后你发现还不如重写一遍。
说了这么多,总之结构化程序设计就是有很多缺点啦!

面向对象程序设计

我们喜欢的程序是,代码的思路清晰、健壮性好利于维护和添加功能、移植性强,这个时候就要请出面向对象程序设计啦。

面向对象程序设计(Object Oriented Programming)作为一种新方法,其本质是以建立模型体现出来的抽象思维过程和面向对象的方法。模型是用来反映现实世界中事物特征的。任何一个模型都不可能反映客观事物的一切具体特征,只能对事物特征和变化规律的一种抽象,且在它所涉及的范围内更普遍、更集中、更深刻地描述客体的特征。通过建立模型而达到的抽象是人们对客体认识的深化。——百度百科

简单地说,面向对象程序设计就是把某个客观事物的共同特点归纳出来,形成一个数据结构对象既表示客观世界问题空间中的某个具体事物,有表示软件系统解空间中的基本元素。
对象 = 属性 + 方法”,对象以 id 为标识,既包含属性(数据),也包含方法(代码),是某一类具体事物的特殊实例。我们怎么来理解呢?

实例:Dog 类


狗狗是我们的好朋友啦,那么对于一只狗它会有什么行为呢?它应该会叫、会坐、会跑,那么我们就来用 python 封装一个 Dog 类。直接扔一段代码:

class Dog():
    def __init__(self,name,breed):
        #初始化一个 Dog 对象,属性有 name 和 breed
        self.name = name
        self.breed = breed

    def sit(self):
        #狗狗蹲下
        print(self.name.title() + " is now sitting.")

    def roll_over(self):
        #狗狗打滚
        print(self.name.title() + " rolled over!")

然后拿去交互式解释器运行下,创建一个 Dog 对象,查看它的属性,并指挥它蹲下和打滚。

Python 面向对象编程

Python是完全面向对象的语言。函数、模块、数字、字符串都是对象。并且完全支持继承、重载、派生、多继承,有益于增强源代码的复用性。Python支持重载运算符和动态类型。相对于Lisp这种传统的函数式编程语言,Python对函数式设计只提供了有限的支持。有两个标准库(functools, itertools)提供了Haskell和Standard ML中久经考验的函数式程序设计工具。——百度百科


python 绝对是个面向对象的编程语言啦,因为 python 中从数值类型到代码模块都是以对象的形式存在的,我们来举 3 个例子,打开交互式解释器。
对于 int 类型,我们查看 “1” 的 id、type、和 dir(可操作性 int 的方法):

接下来是字符类型,查看 “a” 的 id、type、和 dir:

接下来我们看个不一样的,在函数不被调用时,就可以认为是一个对象,也会有一些方法可以操作函数,例如查看 “abs()” 函数的 id、type、和 dir:

对象与方法

所谓“对象”,它实现了属性和方法的封装,这是一种数据抽象的机制,这种方式提高了程序的重用性、灵活性、扩展性。
我们需要怎么创建一个对象呢?例如上文的 Dog 类,那么创建一个 Dog 对象的代码为:

a_dog = Dog("bilibili·狗·德川家康·薛定谔·保留","Husky")

应该不难理解,首先需要制定是什么类,然后传入需要的属性进去,例如 Dog 类需要 2 个属性。

引用方法

类中的函数被称为方法,引用的代码格式为:

<对象名>.<属性名>

例如我们要让一个 Dog 对象完成 sit 动作,其代码为:

a_dog.sit()
  • Python 是一门动态的编程语言,因此对象可以随时增加或删除属性或方法

类的定义与调用

class

要创建一个对象,这个对象的类已经被定义时必要条件,所谓“类(class)” 就是用来描述相同的属性和方法的集合,定义了该集合中每个对象共有的属性和方法,而对象则是类的实例。需要注意的是,对于同一个类,类的对象将会具有相同的属性和方法,但是属性的数据和 id 是不同的
和函数或者 C 语言的结构体有类似之处,类是一系列代码的封装,在 Python 中我们约定俗成,类名需要以大写字母为开头,函数则以小写字母开头,方便我们进行区分。

定义类

定义类我们需要使用 class 语句:

class <类名>
    <一系列方法的调用>

初始化类

直接看代码:

class<类名>
    def __init__(self,<参数表>):
    def 方法名(self,<参数表>):

__ init __

"__ init __" 是一个特殊的方法,每当你需要根据类创建对象时,Python 会自动运行并对对象进行初始化,第一个参数必须是 self。

self

对于一个类,所有的方法中第一个参数一般都是 self,在类的内部,实例化时传入的参数都会直接赋给这个变量。

调用类

当我们调用一个类时,就会创建一个对象,代码:

obj = <类名>(<参数表>)

实例:Force 类

为了方便理解,我们来写一个 “Force” 类。力是矢量,假设现在讨论的力都是二维空间中的力,那么这个力就需要用 x 和 y 两个分量来描述,同时我们还想实现力的合成操作。

类的封装如下:

class Force:
    def __init__(self,x,y):
        # x 和 y 为力在两个坐标轴的分量
        self.fx,self.fy = x,y

    def show(self):
        #展示力的信息
        print("Force<%s,%s>"%(self.fx,self.fy))

    def add(self,force2):
        #力的合成操作
        x = self.fx + force2.fx
        y = self.fy + force2.fy
        return Force(x,y)

让我们来创建几个对象试试,打开交互式解释器:

Special method

特殊方法

特殊方法,也可以称之为魔术方法(Magic merhod),这是类的定义中已经实现了的方法,使用这些方法我们可以轻松地调用 python 的内置操作。所有特殊方法的名称都是以两个下划线“__”开始和结束
这里罗列一些常用的特殊方法:

特殊方法 操作
__ init __ 构造函数,在生成对象时调用
__ del __ 析构函数,释放对象时使用
__ repr __ 打印,转换
__ setitem __ 按照索引赋值
__ getitem __ 按照索引获取值
__ len __ 获得长度
__ cmp __ 比较运算
__ call __ 函数调用
__ add __ 加运算
__ sub __ 减运算
__ mul __ 乘运算
__ truediv __ 除运算
__ mod __ 求余运算
__ pow __ 乘方

构造与解构

这里需要强调一下“__ init __ ” 方法和 “ __ del __”,前者是对象的构造器,用于实例化对象时被自动调用,后者是析构器,用于销毁对象。
这里还是用 Dog 类来理解一下这两个方法的作用:

class Dog():
    def __init__(self,name,breed):
        #初始化一个 Dog 对象,属性有 name 和 breed
        self.name = name
        self.breed = breed

    def __del__(self):
        #调用这个方法时,对象会被销毁
        del self

实例 1 :Force 类

这次我们使用特殊方法来实现 Force 类:

class Force:
    def __init__(self,x,y):
        # x 和 y 为力在两个坐标轴的分量
        self.fx,self.fy = x,y

    def __add__(self,force2):
    #力的合成操作
        x = self.fx + force2.fx
        y = self.fy + force2.fy
        return Force(x,y)

    def __str__(self):
        #展示力的信息
        return "Force<%s,%s>"%(self.fx,self.fy)

    def __mul__(self,n):
        #力扩大 n 倍
        x,y = self.fx * n,self.fy * n
        return Force(x,y)

    def __eq__(self,force2):
        #判读力是否相等
        return (self.fx == force2.fx) and (self.fy == force2.fy)

测试一下看看,启动交互式解释器:

与上文不同的是,现在我们的 Force 类可以使用诸如 “+”和 “ == ” 运算符来实现一些操作了。这是因为 “__ add __ ” 方法能够让我们可以使用 “+” 运算符来操作 Force 对象, “ __ str __ ” 方法能够让我们可以将 Force 对象的一些信息转换为字符串, “ __ mul __ ” 方法能够让我们可以使用 “*” 运算符来操作 Force 对象, “ __ eq __ ” 方法能够让我们可以使用 “ == ” 运算符来对 Force 对象进行相等的判断。

实例 2 :Student 类

列表排序。

sort()

使用 sort() 方法,该方法用于对原列表进行排序,没有返回值,如果指定参数,则使用比较函数指定的比较函数,语法为:

list.sort( key = None, reverse = False)
参数 说明
list 需要操作的列表
key 进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序
reverse 排序规则,reverse = True 降序, reverse = False 升序(默认)

sorted()

使用 sorted() 函数,该函数可对所有可迭代的对象进行排序操作,返回值为重新排序的列表,该函数不影响列表元素在列表中的原始排列顺序,语法为:

sorted(iterable, cmp=None, key=None, reverse=False)
参数 说明
iterable 可迭代对象
cmp 比较的函数,这个具有两个参数,参数的值都是从可迭代对象中取出,此函数必须遵守的规则为,大于则返回1,小于则返回-1,等于则返回0
key 主要是用来进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序
reverse 排序规则,reverse = True 降序 , reverse = False 升序(默认)
  • sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。list 的 sort 方法返回的是对已经存在的列表进行操作,无返回值,而内建函数 sorted 方法返回的是一个新的 list,而不是在原来的基础上进行的操作。
  • sort 与 sorted 内部是用Timsort算法实现的,Timsort是结合了合并排序(merge sort)和插入排序(insertion sort)而得出的排序算法,它在现实中有很好的效率。Tim Peters 在 2002 年设计了该算法并在 Python 中使用(TimSort 是 Python 中 list.sort 的默认实现)。

__ it __ 方法

Python 是一门拓展性很强的语言,对于每个类来说,都可以定义特殊方法。定义 “it” 方法之后,任意的自定义类都可以进行比较,例如对于 Int 类型就进行数值的比较,string 类型就按照字典序来比较。定义的格式为:

def __init__(self,y)

若方法返回 true,则视为对象比 y 要小,排序的时候就要排在前面,反之排在后面。

自定义对象的排序

现在我们就给出 Student 类的代码,我们需要实现 sort() 方法对 Student 类列表的排序。

class Student:
    def __init__(self,name,grade):
        self.name = name
        self.grade = grade

    def __lt__(self,other):
        #按照成绩由大到小排序
        return self.grade > other.grade

    def __str__(self):
        return "(%s,%d)" % (self.name,self.grade)

    __repr__ = __str__

测试一下,打开交互式解释器:

inheritance

不知道大家还记不记得风火轮,就是那个绝技是“九天雷霆双脚蹬”的摩托(笑)。我还记得有一集风火轮想要强化一下火力,所以就进行了升级,加装了“三星连环炮”,之后风火轮就可以提供火力输出了。我们来分析一下,当风火轮想要变强时,我们是否有必要把风火轮拆了重新做?很明显是不用的,因为我的目的是“风火轮 + 三星连环炮”,因此只需要在原有的风火轮身上加装装备即可。

在实际编写代码的时候,我们也会遇到这样的问题。当一个新的类和我过去实现的类具有很大的相似之处时,我是否需要从头开始写?很明显这样很累,做过的事情何必再做一遍嘛。这时,我们可以沿用我原来写的类,然后往里面添加新的特性,这样不就实现了我的目的了嘛。对于编程,复用已有的类来构建新的类的做法就是面向对象编程的继承特性。

类的继承

编写一个类时,不一定需要从头开始,如果你现在想要实现的类中有的部分已经在另一个现有的类实现了,你可以将现成的类继承过来。当一个类继承另一个类时,将自动获得另一个类的所有属性和方法。被继承的类称为父类,新类被称为子类,同时子类还可以定义自己的属性和方法。利用继承衍生出来的新类可以添加或修改一些新的功能。

方法重写

如果从父类继承的方法不满足子类的需求,可以对其进行重写。

实例:Car 类和 ElentricCar 类


首先我们先写一个 Car 类,这个类可以显示一些汽车的基本信息,并且可以查询里程数。

class Car:
    def __init__(self,make,model,year):
        #初始化车的属性
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0    #里程数,初始化为 0

    def get_descriptive_name(self):
        #打印车的相关信息
        print(str(self.year) + ' ' + self.make + ' ' + self.model)

    def read_odometer(self):
        #打印里程信息
        print("This car has " + str(self.odometer_reading) + " miles on it.")

    def updata_odometer(self,mileage):
        #设置里程数,并禁止回调
        if mileage >= self.odometer_reading:
            self.odometer_reading = mileage
        else:
            print("You can't roll back an odometer!")

测试一下,打开交互式解析器。

很成功,接下来我们写一个 ElentricCar 类。

由于 ElentricCar 和 Car 有很多相似之处,因此可以继承。同时 ElentricCar 具有一些 Car 不具有的特性,例如我要加一个电池电量,要多描述一些信息,这时就可以进行方法重写。

class Car:
    def __init__(self,make,model,year):
        #初始化车的属性
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0    #里程数,初始化为 0

    def get_descriptive_name(self):
        #打印车的相关信息
        print(str(self.year) + ' ' + self.make + ' ' + self.model)

    def read_odometer(self):
        #打印里程信息
        print("This car has " + str(self.odometer_reading) + " miles on it.")

    def updata_odometer(self,mileage):
        #设置里程数,并禁止回调
        if mileage >= self.odometer_reading:
            self.odometer_reading = mileage
        else:
            print("You can't roll back an odometer!")

class ElentricCar(Car):
    #继承 Car 类
    def __init__(self,make,model,year):
        #初始化电瓶车的属性
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0    #里程数,初始化为 0
        self.battery_size = 100    #电池电量初始化为 100

    def describe_battery(self):
        #打印电量
        print("This car has a " + str(self.battery_size) + "-KWH battery.")

测试一下,打开交互式解析器。

我们可以看到,ElentricCar 类不仅继承了 Car 类的全套代码,而且还能够拥有自己的一些特性!

参考资料

Python 百度百科
《新标准 C++ 程序设计》郭炜 编著,高等教育出版社
《Python语言基础与应用》 陈斌
《Python编程从入门到实践》————[美]Eric Matthes 著,袁国忠 译,人民邮电出版社
菜鸟教程

posted @ 2020-04-17 21:27  乌漆WhiteMoon  阅读(676)  评论(0编辑  收藏  举报