• 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 著,袁国忠 译,人民邮电出版社
    菜鸟教程

  • 相关阅读:
    吉特仓储管系统(开源WMS)--分享两月如何做到10W+的项目
    吉特仓库管理系统(开源)-如何在网页端启动WinForm 程序
    RequireJS中的require如何返回模块
    RequireJS shim 用法说明
    从ISTIO熔断说起-轻舟网关熔断
    数据库与数据仓库的区别是什么
    API是什么
    要想业务中台建得快,最好用Service Mesh来带
    中台建设之路-中台建设怎么做?建设中台需要具备什么?
    为什么要建设中台
  • 原文地址:https://www.cnblogs.com/linfangnan/p/12720191.html
Copyright © 2020-2023  润新知