面向对象

面向对象

1 面向过程与面向对象

1.1 面向过程

面向过程的编程是面向流程的,就是一步一步的按照过程来进行,把完成某一个需求的全部步骤从头至尾逐步实现;python

根据开发需求,将某些功能独立的代码块封装成一个又一个函数。面向过程最重要的特色就是函数,即把执行一件事的过程拆分红一个又一个步骤,而后将这些步骤变为一个又一个的子函数,再经过主函数按照顺序来调用这些子函数以完成整个事件。程序员

这些子函数须要按照事件的发展顺序,或者程序中代码的编写顺序一步一步的执行下去。编程

面向过程的特色:安全

  1. 注重步骤与过程,不注重职责分工
  2. 若是需求复杂,代码会变得很复杂
  3. 开发复杂项目时,没有固定的套路,开发难度很大

在这里插入图片描述

1.2 面向对象

人们在认识世界时,会将对象简单处理为两个部分——属性和行为。数据结构

对象具备属性,也能够称为变量;正如每一个人都有姓名、年龄、身高、体重等属性,咱们能够用这些数据来描述对象的属性。app

对象具备行为,也能够称为方法;就如同每一个人都要吃饭、睡觉、运动同样;面向对象编程将完成某个功能的代码块定义为方法,方法能够被其它程序调用,也能够被对象自身调用。ssh

面向对象的编程,在完成某一个需求前,首先要肯定都有哪些职责,即要作的事情(方法);而后根据职责肯定不一样的对象,在对象内部封装多个不一样的方法;最后完成的代码,就是顺序的让不一样的对象调用不一样的方法。模块化

面向对象的主要特色是类,类是独立个体。即先将全部须要执行的功能或者函数拆分,而后进行分类,封装到不一样的类中;程序执行的过程当中,经过不一样的类或类的对象来调用不一样的函数以完成程序的运行和执行;把一个事件运行的执行过程变为了类和类中功能的交互,也就是不一样的对象之间的交互,所以咱们也把这种编程称为面向对象程序设计。函数

面向对象的特色:工具

  1. 注重对象和职责,不一样的对象承担不一样的职责
  2. 更加适合应对复杂的需求变化,是专门应对复杂项目开发提供的固定套路
  3. 须要在面向过程的基础上,再学习一些面向对象的语法

在这里插入图片描述

1.3 二者之间的对比

好比对"把大象放进冰箱"进行分析。

面向过程:

  1. 打开冰箱门
  2. 把🐘装入冰箱
  3. 关好冰箱门

针对这三个步骤分别编写三个函数,从而实现整个过程。

面向对象:

须要分析出其中的对象以及这种对象具备的动做。

  • 对象1:大象。动做,被装。
  • 对象2:冰箱。动做,开门,关门。

分别设计两种类,以及两种类所具备的动做。

此时,面向对象设计看起来彷佛要比面向过程设计复杂,后者只须要三个步骤就完成了,可是前者还要设计类,有点麻烦。
可是若是事件不止这三个步骤,冰箱有冰箱的体积、温度,大象也有大象的体积、重量,不一样的大象要放在不一样的冰箱中还要有对应的温度,或者编程过程当中忽然有了新的需求,那么面向过程的程序可能就须要从新设计;可是面向对象的程序由于对象和类已经明确,所以只须要在变化的类中添加相应的属性以及须要作的动做就行,此时,面向对象编程的优点就体现出来了。

再来看一个"下五子棋"的实现例子。

面向过程的设计思路:

首先分析事件的步骤:一、开始游戏 二、黑子先走 三、绘制画面 四、判断输赢 五、轮到白子 六、绘制画面 七、判断输赢 八、返回步骤2 九、输出最后结果。而后把上面每一个步骤分别用一个函数来实现。

面向对象的设计思路:

整个五子棋能够分为:一、黑白双方,这两方的行为是如出一辙的 二、棋盘系统,负责绘制画面 三、规则系统,负责断定诸如犯规、输赢等。第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的变化就要负责在屏幕上显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行断定。

二者对比:

  1. 二者均可以实现代码重用和模块化编程的功能,可是面向对象使用的是类进行封装,所以封装性更强,也更加安全。
  2. 面向过程的编程是根据事情的发展顺序来进行的,注重于编程的步骤,而面向对象的编程注重于对象之间的交互。
  3. 从前期角度来看,面对对象远比面向过程要复杂,可是从维护和扩展功能的角度来看,面对对象远比面向过程要简单!好比上面"把大象放进冰箱"的事件。
  4. 面向对象是以数据为主导,面向过程是以动做为主导。
  5. 面向对象的编程,是关注如何把相关的功能,包括函数和数据有组织地捆绑到一个对象身上,它强调"封装"、"继承"、"多态",把一些数据和相关的操做封装起来包装成对象,使全部的模块达到更好的内聚性和更低的耦合性,使不一样的对象之间减小依赖性,从而提升代码的可复用性,提升编程效率。

2 类与对象

类和对象是面向对象编程的两个核心概念。

2.1 类

类是一群具备相同或类似的特征或行为的对象的统称。类是抽象的,不能直接使用;特征被称为属性,行为被称为方法。

人就是一种类,每一个人——即人这种类的对象,都有姓名、年龄、身高、体重等属性,每一个人也有吃饭、睡觉、运动等行为。类是对象的抽象,对象则是类的实例化、具体化,每一个对象都包括了类中定义的属性和行为。

类就至关因而制造飞机时的图纸,是一个模板,是负责建立对象的。

在这里插入图片描述

2.2 对象

在Python中,对象几乎是无处不在的,咱们以前学习的变量、数据、函数等都是对象。

对象是由类建立出来的一个实例化、具体化的存在。对象是具体的,能够直接使用。

由哪个类建立出来的对象,就拥有在哪个类中定义的属性和方法。

在程序开发中,应该先有类,再有对象。

对象就至关因而根据图纸制造出来的飞机。

在这里插入图片描述

2.3 类和对象的关系

类是模板,对象是根据类这个模板建立出来的,全部应该先有类,再有对象。

类只有一个,而一个类中的对象能够有不少个,不一样的对象可能拥有不一样的属性。

类中定义了什么属性和方法,对象中就有什么属性和方法,不可能多,也不可能少。

2.4 类的设计

在使用面向对象开发前,应该首先分析事件的需求,肯定程序中须要包含哪些类。

在程序开发中,要设计一个类,一般须要知足三个要素

  1. 类名,这类事物的名字,应知足大驼峰命名法;
  2. 属性,这类事物具备什么样的特征;
  3. 方法,这类事物具备什么样的行为。

大驼峰命名法:每个单词的首字母大写;单词与单词之间没有下划线。

2.4.1 类名的肯定

用名词提炼法分析整个业务流程,流程中出现的名词,一般就是找到的类。

2.4.2 属性和方法的肯定

对对象的特征描述,一般能够定义成属性。

对象具备的行为(动词),一般能够定义成方法。

提示:需求中没有涉及到的属性或方法,在设计类时不须要考虑。

3 类的建立与运用

3.1 类的相关概念的介绍

◇ 类:用来描述具备相同属性和方法的对象的集合。它定义了该集合中每一个对象所共有的属性和方法。对象是类的实例。

◇ 对象:经过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

◇ 方法:类中定义的函数。

◇ 实例化:建立一个类的实例,一个类的具体对象。

◇ 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体以外,一般不做为实例变量使用。

◇ 实例变量:定义在方法中的变量,只做用于当前实例的类。

◇ 数据成员:类变量或者实例变量,用于处理类及其实例对象的相关数据。

◇ 方法重写:若是从父类继承的方法不能知足子类的需求,能够对其进行改写,这个改写的过程叫作方法的覆盖,也称做方法的重写。

◇ 继承:指一个派生类继承基类的字段和方法。继承容许把一个派生类的对象做为一个基类对象对待。

3.2 类的定义

面向对象是更大的封装,在一个类中封装多个方法,这样经过这个类建立出来的对象,就能够直接调用这些方法了。

Python中使用class关键字来声明一个类,class中有成员属性和成员方法。

类名和变量名同样区分大小写,字母相同但大小写不一样的类是不相同的类。

在Python中定义一个只包含方法的类,格式以下:

class 类名:
	def 方法1(self, 参数列表):
		代码块
	def 方法2(self, 参数列表):
		代码块
    ...

方法的定义格式和以前学习过的函数几乎是同样的,主要的区别在于方法的第一个参数必须是self。

注意,类名的命名规则要符合大驼峰命名法。

定义完类以后,要使用这个类来建立对象,格式以下:

对象变量 = 类名()

建立对象和建立变量相似,须要先声明对象属于哪一个类,同时指明对象名称。

类的定义
print('定义类和对象')

class EmptyClass:  # 建立类
    pass

empty = EmptyClass()  # 建立对象变量
print(type(empty))
执行结果以下:
定义类和对象
<class '__main__.EmptyClass'>
3.2.1 第一个面向对象程序

需求:小猫爱吃鱼,小猫要喝水

分析:

  1. 定义一个猫类Cat
  2. 定义两个方法,eat和drink
  3. 按照需求,不须要定义属性

在这里插入图片描述

程序
print('第一个面向对象程序')

class Cat:
    """这是一个猫类"""

    def eat(self):
        print('小猫爱吃鱼')  # 定义eat方法

    def drink(self):
        print('小猫要喝水')  # 定义drink方法

tom = Cat()  # 定义对象变量tom

tom.eat()  # 调用eat方法
tom.drink()  # 调用drink方法
执行结果以下:
第一个面向对象程序
小猫爱吃鱼
小猫要喝水
3.2.2 设置对象属性

在Python中,要给对象设置属性,很是的容易,只须要在类的外部的代码中直接经过 "对象变量名.属性名 = 赋值" 格式,便可设置属性;可是不推荐使用,由于对象属性的封装应该在类的内部实现。

注意,这种设置属性的方式虽然简单,可是在程序开发中并不推荐使用。

设置对象属性
class Cat:
    """这是一个猫类"""

    def eat(self):
        print('小猫爱吃鱼')  # 定义eat方法

    def drink(self):
        print('小猫在喝水')  # 定义drink方法

tom = Cat()  # 定义对象变量tom

tom.eat()  # 调用eat方法
tom.drink()  # 调用drink方法

tom.name = 'Tom'  # 设置名字属性
tom.age = 5  # 设置年龄属性
print('tom的名字叫:%s' % tom.name)
print('%s的年龄为:%d岁' % (tom.name, tom.age))
执行结果以下:
小猫爱吃鱼
小猫在喝水
tom的名字叫:Tom
Tom的年龄为:5岁
3.2.3 self的使用

由哪个对象调用的方法,方法内的self就是哪个对象的引用。

在类封装的方法内部,self就表示当前调用方法的对象自己。

调用方法时,程序员不须要传递self参数;在方法内部,能够经过 "self." 访问对象的属性,也能够经过 "self." 调用其它的对象方法。

在类的外部,经过 "变量名." 访问对象的属性和方法。

在类封装的方法中,经过 "self." 访问对象的属性和方法。

self的使用
print('self的使用')

class Cat:

    def eat(self):
        print('%s爱吃鱼' % self.name)  # 用self来调用属性

tom = Cat()
tom.name = 'Tom'
tom.eat()  # 对象为tom,所以调用方法eat时,self指的是tom

lazy_cat = Cat()
lazy_cat.name = '大懒猫'
lazy_cat.eat()  # 对象为lazy_cat,所以调用方法eat时,self指的是lazy_cat
执行结果以下:
self的使用
Tom爱吃鱼
大懒猫爱吃鱼
3.3 类的初始化方法
3.3.1 定义和使用初始化方法

当使用 "类名()" 建立对象时,会自动执行如下操做:

  1. 为对象变量分配内存空间——建立对象
  2. 为对象的属性设置初始值——初始化方法(__ init __)

这个初始化方法就是__ init __方法(init先后都有两个连续的短下划线), 它是对象的内置方法。

__ init __方法是专门用来定义一个类具备哪些属性的方法!

定义和使用初始化方法
print('定义和使用初始化方法')

class Dog:

    def __init__(self):  # 定义初始化方法
        print('汪汪汪!')

dog = Dog()  # 建立对象变量dog
执行结果以下:
定义和使用初始化方法
汪汪汪!

当一个类定义了 __ init __ 方法,类在实例化时会自动调用 __ init __ 方法,用于建立新的类实例。在上面的例子中,新的实例dog被建立,同时执行了初始化方法,运行了print函数。

注意,初始化方法的返回值必须是 "None" 。

3.3.2 在初始化方法中定义属性

在初始化方法中,咱们能够初始化一些属性。

属性(或者叫成员变量、类变量)必须使用 "self." 的方式赋值,不能直接定义变量;直接定义的变量的生命周期只会在函数内体现,函数执行完变量就会被销毁。

在 __ init __ 方法中使用 "self.属性名 = 属性的初始值" 格式就能够定义属性。

定义属性以后,再使用类建立对象,那么被建立的对象都会拥有该属性。

初始化属性
print('初始化属性')

class Dog:

    def __init__(self, name):  # 定义初始化方法,并定义name形参
        self.name = name  # 初始化名字属性
        self.age = 5  # 初始化年龄属性

dog = Dog('旺财')  # 建立对象变量dog,并传入name实参

print(dog.name)  # 输出名字属性
print(dog.age)  # 输出年龄属性
执行结果以下:
初始化属性
旺财
5

其实函数 __ init __ 的第一个参数 "self" 指的就是实例自己,在C++等语言中对应的就是 "this" 指针,能够理解为对实例的属性进行赋值。Python在调用 __ init __ 函数时会自动地添加实例做为函数的第一个参数。

咱们可使用参数设置属性初始值来对 __ init __ 方法进行进一步的改造:

  1. 把想要设置的属性值,定义成 __ init __ 方法的参数
  2. 在方法内部使用 "self.属性名 = 形参" 接收外部传递的参数
  3. 在建立对象时,使用 "对象变量名 = 类名(实参)" 来调用属性
3.3.3 类中的方法

在类中定义的函数咱们称之为方法。类中的方法和函数定义的方式基本相同,主要区别就在于方法必定要定义在类里面,而且第一个参数必须是 "self" ,其它方面和函数没什么差别。

类中的方法
print('类中的方法')

class Dog:

    def __init__(self, name):  # 定义初始化方法
        self.name = name  # 定义name属性

    def shout(self):  # 定义shout方法
        print('汪汪汪!我是%s' % self.name)

dog = Dog('旺财')  # 建立对象变量dog
dog.shout()  # 调用shout方法
执行结果以下:
类中的方法
汪汪汪!我是旺财
3.4 Python的内置方法和属性
3.4.1 __ del __ 方法

定义了 __ del __ 方法以后,当一个对象要从内存中销毁,销毁以前, __ del __ 方法会被自动调用。

若是想在对象被销毁前,再作一些事情,就可使用 __ del __ 方法。

__ del __ 方法
print('__del__方法')

class Cat:

    def __init__(self, name):
        self.name = name
        print('%s来了!' % self.name)

    def __del__(self):
        print('%s走了!' % self.name)  # 执行完变量的全部相关代码,而后才执行此句

tom = Cat('汤姆')
print(tom.name)
执行结果以下:
__del__方法
汤姆来了!
汤姆
汤姆走了!
生命周期:
  • 一个对象从类的实例化建立,生命周期开始
  • 一个对象的 __ del __ 方法一旦被调用,生命周期结束
  • 在对象的生命周期内,能够访问对象属性,或者让对象调用方法
3.4.2 __ str __ 方法

在Python中,使用print函数输出对象变量时,默认状况下,会输出这个变量引用的对象是由哪个类建立的对象,以及这个变量在内存中的地址(用十六进制表示)。

若是在开发中,但愿在使用print函数输出对象变量时,可以打印自定义的内容,那么就可使用 __ str __ 这个内置方法。

注意, __ str __ 方法必须返回一个字符串

__ str __ 方法
print('__str__方法')

class Cat:

    def __init__(self, name):
        self.name = name
        print('%s来了!' % self.name)

    def __del__(self):
        print('%s走了!' % self.name)  # 执行完变量的全部相关代码,而后才执行此句

    def __str__(self):
        return '我是小猫:%s' % self.name  # 必须返回一个字符串

tom = Cat('汤姆')
print(tom)  # 由于定义了__str__方法,因此这里打印对象变量tom,只会输出方法里自定义的内容
执行结果以下:
__str__方法
汤姆来了!
我是小猫:汤姆
汤姆走了!
3.5 私有属性和私有方法
3.5.1 私有属性

实例能够轻松地获取和修改方法中定义的属性的值,可是有时候咱们须要限制实例随意修改属性,这时候就要用到私有属性。

定义私有属性很简单,只要在定义属性名字的时候使用两条下划线做为开头,Python解释器就会认为这个属性是私有的,外部不能随便访问这个属性。

私有属性是只能在类内部被操做的属性,实例不能直接访问。也就是说,在对象的方法内部,是能够访问对象的私有属性的,但在外部不能。

在平时的实际项目中,咱们可使用这个特性保护一些不想让用户随意修改的属性。

私有属性
print('私有属性')

class Dog:

    def __init__(self, name):
        self.__name = name  # 定义name私有属性
        self.__age = None  # 定义age私有属性
        print(self.__name, '取名成功')

    def set_age(self, age):
        if not isinstance(age, int):
            print('年龄必须是整型!')
            return False

        if age <= 0:
            print('年龄必须大于0!')
            return False

        self.__age = age

    def shout(self):
        print('汪汪汪!我今年%s岁' % self.__age)

dog = Dog('旺财')
dog.set_age('hello')
dog.set_age(-20)
dog.set_age(5)
dog.shout()
'''
print(dog.__name)
这是错误的语法,由于__name是私有属性,变量不能直接访问私有属性
在上面的例子中,__age是私有属性,实例化后只能经过set_age方法设置年龄,变量不能直接访问
'''
执行结果以下:
私有属性
旺财 取名成功
年龄必须是整型!
年龄必须大于0!
汪汪汪!我今年5岁
3.5.2 私有方法

与私有属性同样,私有方法只能在类内部被调用,实例不能直接调用。

定义私有方法的方式跟定义私有属性同样,只要在定义方法名字的时候使用两条下划线做为开头便可。

调用私有方法的格式为 "对象变量._类名__方法名(参数)" 。这是由于,在python解释器中,全部以双下划线开头的方法都会被翻译成方法前面加上单下划线和类名的形式。

私有方法
print('私有方法')

class Dog:

    def __animal(self, name):  # 定义私有方法
        print('个人名字叫:%s' % name)

dog = Dog()  # 建立对象变量
dog._Dog__animal('旺财')  # 调用私有方法
执行结果以下:
私有方法
个人名字叫:旺财

4 封装、继承和多态

面向对象编程具备三大特性——封装性、继承性和多态性,这些特性使程序设计具备良好的扩展性和健壮性。

  1. 封装:根据职责将属性和方法封装到一个抽象的类中
  2. 继承:实现代码的重用,相同的代码不须要重复的编写
  3. 多态:不一样的对象调用相同的方法,产生不一样的执行结果,增长代码的灵活度
4.1 封装

封装是面向对象编程的一大特色,是面向对象编程的第一步,它将属性和方法封装到一个抽象的类中,外部使用类建立对象,而后让对象调用方法,对象方法的细节都被封装在类的内部。

4.1.1 封装案例——小明爱跑步

需求:

  1. 小明体重75.0公斤
  2. 小明每次跑步会减肥0.5公斤
  3. 小明每次吃东西会增肥1公斤

对需求进行分析:首先用名词提炼法把需求中的名词提炼出来,本案例中有个 "小明" 的名字,所以咱们能够定义一个 "人" 的类,在类中定义一个name属性,用来存储小明这个名字;再接着看,小明还有 "体重" 这个特征,因此咱们再定义一个weight属性,用来存储小明的体重;明确完小明的特征属性以后,再看案例中的动词,案例中有 "跑步" 、 "吃东西" 两个动词,所以能够在类中定义两个方法run(self)和eat(self);同时在这个案例中,咱们能够用 __ init __ 方法来简化对象的建立,用 __ str __ 方法简化小明这个对象的输出。

在这里插入图片描述

封装案例——小明爱跑步
class Person:

    def __init__(self, name, weight):
        # self.属性名 = 形参
        self.name = name  # 定义name属性
        self.weight = weight  # 定义weight属性

    def __str__(self):  # 用__str__方法存储自定义内容
        return '个人名字叫:%s  体重是 %.2f 公斤' % (self.name, self.weight)

    def run(self):
        print('%s爱跑步,运动令人健康!' % self.name)
        self.weight -= 0.5

    def eat(self):
        print('%s是吃货,吃完这顿再减肥~' % self.name)
        self.weight += 1

xiaoming = Person('小明', 75.0)  # 建立对象变量xiaoming

xiaoming.run()  # 调用run()方法
print(xiaoming)  # 打印自定义内容

xiaoming.eat()  # 调用eat()方法
print(xiaoming)  # 打印自定义内容
执行结果以下:
小明爱跑步,运动令人健康!
个人名字叫:小明  体重是 74.50 公斤
小明是吃货,吃完这顿再减肥~
个人名字叫:小明  体重是 75.50 公斤

在对象的方法内部,是能够直接访问对象属性的;同一个类建立的多个对象之间,属性互不干扰。

4.1.2 封装案例——摆放家具

需求:

  1. 房子(House)户型总面积家具名称列表

    ● 新房子没有任何的家具

  2. 家具(HouseItem)名字占地面积,其中

    席梦思(bed)占地 4 平米

​ ● 衣柜(chest)占地 2 平米

​ ● 餐桌(table)占地 1.5 平米

  1. 将以上三件家具添加到房子中

  2. 打印房子时,要求输出:户型总面积剩余面积家具名称列表

在这里插入图片描述

剩余面积

  1. 在建立房子对象时,定义一个剩余面积的属性,初始值和总面积相等
  2. 当调用add_item方法,向房间添加家具时,剩余面积 -= 家具面积

先开发哪一个类?应该先开发家具类(被使用的类,一般应该先开发)

  1. 家具简单
  2. 房子要使用到家具

添加家具

  1. 判断家具的面积是否超过剩余面积,若是超过,应提示不能添加这件家具
  2. 将家具的名称添加到家具名称列表中
  3. 用房子的剩余面积 - 家具面积
封装案例——摆放家具
class HouseItem:  # 定义家具类

    def __init__(self, name, area):
        self.name = name  # 定义name属性
        self.area = area  # 定义area属性

    def __str__(self):
        return '"%s"占地 %.2f 平米' % (self.name, self.area)


class House:  # 定义房子类

    def __init__(self, house_type, area):
        self.house_type = house_type  # 定义house_type属性
        self.area = area  # 定义area属性
        self.free_area = area  # 剩余面积初始值就等于总面积
        self.item_list = []  # 家具名称列表初始值为空列表

    def __str__(self):
        # Python可以自动的将一对括号内的代码链接在一块儿
        return ('户型:%s\n总面积:%.2f\n剩余面积:%.2f\n家具名称列表:%s'
                % (self.house_type, self.area,
                   self.free_area, self.item_list))

    def add_item(self, item):
        print('添加家具:%s' % item.name)

        if item.area > self.free_area:  # 判断家具的面积是否超过剩余面积
            print('%s的面积已超过剩余面积,没法添加该家具' % item.name)
            return

        self.item_list.append(item.name)  # 将家具名称添加到家具名称列表中
        self.free_area -= item.area  # 计算剩余面积

bed = HouseItem('席梦思', 4)  #建立对象变量bed
chest = HouseItem('衣柜', 2)  #建立对象变量chest
table = HouseItem('餐桌', 1.5)  #建立对象变量table

print(bed)
print(chest)
print(table)

print('---------')

my_home = House('三室两厅', 120)  # 建立对象变量my_home

my_home.add_item(bed)  # 调用add_item()方法,并传入bed参数
print(my_home)

print('---------')

my_home.add_item(chest)  # 调用add_item()方法,并传入chest参数
print(my_home)

print('---------')

my_home.add_item(table)  # 调用add_item()方法,并传入table参数
print(my_home)
执行结果以下:
"席梦思"占地 4.00 平米
"衣柜"占地 2.00 平米
"餐桌"占地 1.50 平米
---------
添加家具:席梦思
户型:三室两厅
总面积:120.00
剩余面积:116.00
家具名称列表:['席梦思']
---------
添加家具:衣柜
户型:三室两厅
总面积:120.00
剩余面积:114.00
家具名称列表:['席梦思', '衣柜']
---------
添加家具:餐桌
户型:三室两厅
总面积:120.00
剩余面积:112.50
家具名称列表:['席梦思', '衣柜', '餐桌']
4.1.3 封装案例——士兵突击

一个对象的属性能够是另外一个类建立的对象。

在定义属性时,若是不知道设置什么初始值,能够设置为 "None" 。

需求:

  1. 士兵许三多有一把AK47
  2. 士兵能够开火
  3. 发射子弹
  4. 装填子弹——增长子弹数量

在这里插入图片描述

封装案例——士兵突击
class Gun:  # 定义枪类

    def __init__(self, model):
        self.model = model  # 定义model属性,表示枪的型号
        self.bullet_count = 0  # 定义bullet_count属性,表示子弹的数量

    def add_bullet(self, count):  # 定义add_bullet()方法,表示装填子弹的动做
        self.bullet_count += count
        print('枪里有 %s 颗子弹' % self.bullet_count)

    def shoot(self):  # 定义shoot()方法,表示发射子弹的动做
        if self.bullet_count <= 0:  # 判断子弹数量
            print('%s没有子弹了...' % self.model)
            return

        self.bullet_count -= 1  # 发射子弹,子弹数量-1

        print('突突突...子弹剩余 %s 颗' % self.bullet_count)


class Soldier:  # 定义士兵类

    def __init__(self, name):
        self.name = name  # 定义name属性
        self.gun = None  # 定义gun属性
        print('士兵%s有一把%s!' % (self.name, ak47.model))

    def fire(self):  # 定义fire()方法,表示开火的动做
        if self.gun == None:  # 判断士兵是否有枪
            print('%s尚未枪...' % self.name)
            return

        print('冲啊...')

        self.gun.add_bullet(50)  # 让枪装填子弹
        self.gun.shoot()  # 让枪发射子弹

ak47 = Gun('AK47')  # 建立枪对象

xusanduo = Soldier('许三多')  # 建立士兵对象

xusanduo.gun = ak47  # 将ak47对象赋值给xusanduo.gun这个属性

xusanduo.fire()  # 调用fire()方法
执行结果以下:
士兵许三多有一把AK47!
冲啊...
枪里有 50 颗子弹
突突突...子弹剩余 49 颗
4.1.4 身份运算符

身份运算符用于比较两个对象的内存地址是否一致。

在Python中,身份运算符有两个:

  1. is,is是判断两个标识符是否是引用同一个对象,x is y 相似于 id(x) == id(y)
  2. is not,is not是判断两个标识符是否是引用不一样对象,x is not y 相似于 id(x) != id(y)

is用于判断两个变量引用对象是否为同一个。

==用于判断引用变量的值是否相等。

在Python中,针对 "None" 比较时,建议使用 "is" 来判断。

>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> b == a
True
>>> b is a
False
4.2 继承

继承的基本思想是,在一个类的基础上定义出另外一个新的类,这个新的类不只能够继承原来类的全部属性和方法,还能够增长新的属性和方法;原来的类被称为父类,新的类被称为子类。

4.2.1 单继承

父类的定义和通常类的定义同样,子类的定义格式以下:

# SubClass为子类的名字,BaseClass为父类的名字
class SubClass(BaseClass1, BaseClass2, ...)
	代码块

在这里插入图片描述

子类的定义
class Animal:  # 定义父类Animal

    def eat(self):
        print('能吃!')

    def drink(self):
        print('能喝!')

    def run(self):
        print('能跑!')

    def sleep(self):
        print('能睡!')


class Dog(Animal):  # 定义Animal的子类Dog

    def bark(self):  # 新增bark()方法
        print('能叫!')


class XiaoTianQuan(Dog):  # 定义Dog的子类XiaoTianQuan

    def fly(self):  # 新增fly()方法
        print('能飞!')


class Cat(Animal):  # 定义Animal的子类Cat

    def catch(self):  # 新增catch()方法
        print('能抓老鼠!')

print('Animal类的对象:')

animal = Animal()  # 建立animal对象

animal.eat()
animal.drink()
animal.run()
animal.sleep()

print('---------')
print('Dog类的对象:')

dog = Dog()  # 建立dog对象

dog.eat()
dog.drink()
dog.run()
dog.sleep()
dog.bark()

print('---------')
print('XiaoTianQuan类的对象:')

xiaotianquan = XiaoTianQuan()  # 建立xiaotianquan对象

xiaotianquan.eat()
xiaotianquan.drink()
xiaotianquan.run()
xiaotianquan.sleep()
xiaotianquan.bark()
xiaotianquan.fly()

print('---------')
print('Cat类的对象:')

cat = Cat()  # 建立cat对象

cat.eat()
cat.drink()
cat.run()
cat.sleep()
cat.catch()
执行结果以下:
Animal类的对象:
能吃!
能喝!
能跑!
能睡!
---------
Dog类的对象:
能吃!
能喝!
能跑!
能睡!
能叫!
---------
XiaoTianQuan类的对象:
能吃!
能喝!
能跑!
能睡!
能叫!
能飞!
---------
Cat类的对象:
能吃!
能喝!
能跑!
能睡!
能抓老鼠!

上面的例子中,涉及到一些专业术语:

  1. Dog类是Animal类的子类,Animal类是Dog类的父类,Dog类从Animal类继承
  2. Dog类是Animal类的派生类,Animal类是Dog类的基类,Dog类从Animal类派生
4.2.2 方法重写

当父类的方法不能知足子类的需求时,能够在子类中对父类的方法进行重写。

方法重写有两种状况:

  1. 覆盖父类的方法。若是在开发中,父类的方法实现和子类的方法实现彻底不一样,就可使用覆盖的方式,在子类中从新编写父类的方法实现;具体的实现方式,就是在子类中定义一个和父类中同名的方法,而且实现;重写以后,在运行时只会调用在子类中重写的方法,而再也不会调用父类中名字相同的方法。
  2. 对父类方法进行扩展。若是在开发中,子类的方法实现包含父类的方法实现,即父类本来封装的方法实现是子类方法的一部分,就可使用扩展的方式;具体的实现方式,是在子类中重写父类的方法,在须要的位置使用 "super().父类方法" 来调用父类的方法,在其它位置针对子类的需求编写子类特有的代码实现。

关于super

  • 在Python中是一个特殊的类
  • super()就是使用super类建立出来的对象
  • 最常使用的场景就是在重写父类方法时,调用在父类中封装的方法实现
方法重写——覆盖
class Animal:  # 定义父类Animal

    def eat(self):
        print('能吃!')

    def drink(self):
        print('能喝!')

    def run(self):
        print('能跑!')

    def sleep(self):
        print('能睡!')


class Dog(Animal):  # 定义Animal的子类Dog

    # 若是子类中,重写了父类的方法,在使用子类对象调用方法时,会调用子类中重写的方法
    def eat(self):
        print('小狗生病了,吃不下东西')

    def run(self):
        print('小狗腿折了,不能跑,只能走')

dog = Dog()  # 建立dog对象

dog.eat()  # 实如今子类中重写的方法
dog.drink()  # 实现从父类中继承的方法
dog.run()  # 实如今子类中重写的方法
dog.sleep()  # 实现从父类中继承的方法
执行结果以下:
小狗生病了,吃不下东西
能喝!
小狗腿折了,不能跑,只能走
能睡!
方法重写——扩展
class Animal:  # 定义父类Animal

    def eat(self):
        print('能吃!')

    def drink(self):
        print('能喝!')

    def run(self):
        print('能跑!')

    def sleep(self):
        print('能睡!')


class Dog(Animal):  # 定义Animal的子类Dog

    def eat(self):
        super().eat()  # 使用"super().父类方法"调用本来在父类中封装的eat()方法
        print('比隔壁邻居家的狗还能吃!!')

    def run(self):
        super().run()  # 使用"super().父类方法"调用本来在父类中封装的run()方法
        print('比隔壁邻居家的狗跑的还要快!!!')

dog = Dog()  # 建立dog对象

dog.eat()  # 实如今子类中重写的方法
dog.drink()  # 实现从父类中继承的方法
dog.run()  # 实如今子类中重写的方法
dog.sleep()  # 实现从父类中继承的方法
执行结果以下:
能吃!
比隔壁邻居家的狗还能吃!!
能喝!
能跑!
比隔壁邻居家的狗跑的还要快!!!
能睡!

调用父类方法还有另一种方式,在Python 2.x中,若是须要调用父类的方法,可使用 "父类名.方法(self)" 的方式。这种方式目前在Python 3.x中也被支持,但这种方式并不推荐使用,由于一旦父类发生变化,方法调用位置的类名一样须要修改。

4.2.3 多继承

在Python中,子类能够拥有多个父类,而且具备全部父类的属性和方法。

例如:孩子会继承本身父亲和母亲的特性。

在这里插入图片描述

多继承的语法格式以下:

class 子类名(父类名1, 父类名2, ...):
	代码块
多继承
print('多继承')

class A:  # 定义父类A

    def test(self):
        print('我是C的父类:A')


class B:  # 定义父类B

    def demo(self):
        print('我是C的父类:B')


class C(A, B):  # 定义A、B的子类C

    pass  # 占位语句

c = C()  # 建立子类C的对象

c.test()  # 调用父类A的方法
c.demo()  # 调用父类B的方法
执行结果以下:
多继承
我是C的父类:A
我是C的父类:B

Python中的MRO——方法搜索顺序

Pyhton中针对类提供了一个内置属性 "__ mro __" ,这个属性能够查看方法的搜索顺序。

MRO是method resolution order的缩写,主要用于查看多继承时方法、属性的调用路径。

使用格式为 "print(类名.__ mro __)" 。

  • 在搜索方法时,是按照__ mro __的输出结果从左至右查找的
  • 若是在当前类中找到方法,就直接执行,再也不搜索
  • 若是没有找到,就查找下一个类中是否有搜索的方法,若是找到,就直接执行,再也不搜索
  • 若是找到最后一个类,尚未找到搜索的方法 ,程序会报错

在这里插入图片描述

在开发时,应该尽可能避免如上图中这种容易产生混淆的状况。若是父类之间存在同名的属性或方法,应该尽可能避免使用多继承。

MRO——方法搜索顺序
print('MRO——方法搜索顺序')

class A:  # 定义父类A

    def test(self):
        print('A --- test方法')

    def demo(self):
        print('A --- demo方法')


class B:  # 定义父类B

    def test(self):
        print('B --- test方法')

    def demo(self):
        print('B --- demo方法')


class C(B, A):  # 定义A、B的子类C

    pass  # 占位语句

c = C()  # 建立子类C的对象

c.test()  # 调用父类A的方法
c.demo()  # 调用父类B的方法

print(C.__mro__)  # 使用内置属性__mro__查看子类对象调用方法的顺序

'''
先查看当前类,即C类,C类中没有要调用的方法,查看下一个类
由于在定义C类时,C的父类中,B类排在A类前面,因此先查看B类
B类中有要调用的方法,所以调用B类中的方法,调用完再也不搜索
'''
执行结果以下:
MRO——方法搜索顺序
B --- test方法
B --- demo方法
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

新式类与旧式(经典)类

object是Python为全部对象提供的基类,object类中提供有一些内置的属性和方法,可使用dir()函数查看。

  • 新式类:以object为基类的类,推荐使用
  • 经典类:不以object为基类的类,不推荐使用

在Python 3.x中定义类时,若是没有指定父类,会默认使用object做为该类的基类,即Python 3.x中定义的类都是新式类。

在Python 2.x中定义类时,若是没有指定父类,则不会以object做为基类。

新式类和经典类在多继承时,会影响到方法的搜索顺序。

为了保证编写的代码可以同时在Python 2.x和Python 3.x中运行,从此在定义类时,若是没有父类,建议统一继承自object。

格式以下:

class 类名(object):
	代码块
object的内置属性和方法
print('object的内置属性和方法:')

class A(object):

    pass

a = A()

print(dir(a))  # dir(对象名)用来查看对象能够访问和调用的属性和方法
执行结果以下:
object的内置属性和方法:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
4.3 多态

多态是指不一样的子类对象调用相同的父类方法,会产生不一样的执行结果。

  • 多态以继承和重写父类方法为前提
  • 多态能够增长代码的灵活度

多态案例1

在这里插入图片描述

多态案例1
print('多态案例1')

class Human:  # 定义父类

    def work(self):
        print('人类要辛勤工做~')


class Programmer(Human):  # 定义human的子类

    def work(self):  # 重写父类的方法
        print('程序员要编写程序!!')


class Designer(Human):  # 定义human的子类

    def work(self):  # 重写父类的方法
        print('设计师要设计做品!!')

human = Human()
human.work()  # 调用父类的work()方法

programmer = Programmer()
programmer.work()  # 调用子类Programmer的work()方法

designer = Designer()
designer.work()  # 调用子类Designer的work()方法
执行结果以下:
多态案例1
人类要辛勤工做~
程序员要编写程序!!
设计师要设计做品!!

多态案例2

需求:

  1. 在Dog类中封装game()方法
    • 普通狗只是简单的玩耍
  2. 定义XiaoTianDog类继承自Dog类,而且重写game()方法
    • 哮天犬须要在天上玩耍
  3. 定义Person类,而且封装一个game_with_dog()方法
    • 在方法内部,直接让狗对象调用game()方法

在这里插入图片描述

多态案例2
print('多态案例2')

class Dog(object):  # 定义父类Dog

    def __init__(self, name):  # 定义初始化方法,并定义name参数
        self.name = name  # 定义name属性,并把参数赋值给它

    def game(self):  # 定义game()方法
        print('%s蹦蹦跳跳的去玩耍...' % self.name)


class XiaoTianDog(Dog):  # 定义Dog的子类XiaoTianDog

    def game(self):  # 重写父类的game()方法
        print('%s飞到天上去玩耍...' % self.name)


class Person(object):  # 定义Person类

    def __init__(self, name):  # 定义初始化方法,并定义name参数
        self.name = name  # 定义name属性,并把参数赋值给它

    def game_with_dog(self, dog):  # 定义game_with_dog()方法,并定义dog参数
        print('%s和%s快乐的玩耍...' % (self.name, dog.name))

        dog.game()  # 在方法内部调用game()方法

wangcai = Dog('旺财')  # 建立Dog类对象

fly_wangcai = XiaoTianDog('飞天旺财')  # 建立XiaoTianDog类对象

xiaoming = Person('小明')  # 建立Person类对象

xiaoming.game_with_dog(wangcai)  # 调用game_with_dog()方法,并传入实参wangcai

print('---------')

xiaoming.game_with_dog(fly_wangcai)  # 调用game_with_dog()方法,并传入实参fly_wangcai
执行结果以下:
多态案例2
小明和旺财快乐的玩耍...
旺财蹦蹦跳跳的去玩耍...
---------
小明和飞天旺财快乐的玩耍...
飞天旺财飞到天上去玩耍...

5 知识拓展

5.1 类的结构
5.1.1 术语——实例
  1. 使用面向对象开发,第一步是设计类

  2. 使用 "类名()" 建立对象,建立对象的动做有两步:

    • 在内存中为对象分配空间

    • 调用初始化方法 __ init __ 为对象初始化

  3. 对象建立后,内存中就有了一个对象的实实在在的存在——实例

在这里插入图片描述

所以,一般也会把:

  1. 建立出来的对象叫作类的实例
  2. 建立对象的动做叫作实例化
  3. 对象的属性叫作实例属性
  4. 对象调用的方法叫作实例方法

在程序执行时:

  1. 对象各自拥有本身的实例属性

  2. 调用对象方法,能够经过 "self."

    • 访问本身的属性

    • 调用本身的方法

每个对象都有本身独立的内存空间,保存各自不一样的属性。

多个对象的方法,在内存中只有一份,在调用方法时,须要把对象的引用传递到方法内部。

5.1.2 类是一个特殊的对象

Python中一切皆为对象。

在Python中,类是一个特殊的对象——类对象。

在程序运行时,类一样会被加载到内存;类对象在内存中只有一份,使用一个类能够建立出不少个对象实例。

除了封装实例的属性和方法外,类对象还能够拥有本身的属性和方法——类属性类方法

能够经过 "类名." 的方式访问类的属性或调用类的方法。

在这里插入图片描述

5.2 类属性(类变量)

类属性就是在类对象中定义的属性。一般用来记录与这个类相关的特征,类属性不会用于记录具体对象的特征。

类属性(类变量)是一种在定义类的时候定义的变量,它跟实例变量的区别在于,类变量不须要实例化(即建立对象)就能直接使用。

类变量也能够在实例中被调用。

注意,实例不能修改类变量。

案例需求:

  1. 定义一个工具类
  2. 每件工具都有本身的name
  3. 须要知道使用该类建立了多少个工具对象

在这里插入图片描述

类属性
print('类属性')

class Tool(object):
    
    # 要定义类属性,在类名下方使用赋值语句就能够
    count = 0  # 使用赋值语句,定义类属性,记录建立工具对象的总数

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

        Tool.count += 1  # 让类属性的值+1

# 建立工具对象
tool1 = Tool('斧头')
tool2 = Tool('锯子')
tool3 = Tool('铁锹')

print('一共建立了 %d 个工具对象!' % Tool.count)  # 输出工具对象总数
执行结果以下:
类属性
一共建立了 3 个工具对象!

属性的获取机制

在Python中,属性的获取存在一个向上查找机制。

在这里插入图片描述

所以,访问类属性有两种方式:

  1. 类名.类属性
  2. 对象名.类属性(不推荐)

注意,若是使用 "对象名.类属性 = 值" 赋值语句,只会给对象添加一个属性(即实例属性),而不会影响到类属性的值。

5.3 类方法

类方法就是针对类对象定义的方法。在类方法内部能够直接访问类属性或调用其它的类方法。

类方法须要用装饰器 "@classmethod" 来标识,告诉解释器这是一个类方法。

类方法的第一个参数再也不是self,而是cls;这个参数和实例方法中的self参数相似;由哪个类调用的方法,方法内的cls就是哪个类的引用。

经过 "类名." 调用类方法时,不须要传递cls参数。

在方法内部,能够经过 "cls." 访问类的属性,也能够经过 "cls." 调用其它的类方法。

定义类方法的语法格式以下:

@classmethod
def 类方法名(cls):
	代码块
类方法
print('类方法')

class Tool(object):

    count = 0  # 使用赋值语句,定义类属性,记录建立工具对象的总数

    @classmethod  # 装饰器,标识类方法
    def show_tool_count(cls):  # 定义类方法
        print('工具对象的总数为:%d' % cls.count)

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

        Tool.count += 1  # 让类属性的值+1

# 建立工具对象
tool1 = Tool('斧头')
tool2 = Tool('锯子')

Tool.show_tool_count()  # 调用类方法,cls参数不用传入实参
执行结果以下:
类方法
工具对象的总数为:2
5.4 静态方法

在开发时,若是须要在类中封装一个方法,这个方法既不须要访问实例属性或调用实例方法,也不须要访问类属性或调用类方法,这个时候,就能够把这个方法封装成一个静态方法。

静态方法须要用装饰器 "@staticmethod" 来标识,告诉解释器这是一个静态方法。

经过 "类名." 调用静态方法。

静态方法不须要有相似self的参数。

定义静态方法的语法格式以下:

@staticmethod
def 静态方法名():
	代码块
静态方法
print('静态方法')

class Dog(object):

    @staticmethod  # 装饰器,标识静态方法
    def run():  # 定义静态方法
        print('小狗在跑...')

Dog.run()  # 调用静态方法,不须要建立对象,直接调用
执行结果以下:
静态方法
小狗在跑...
相关文章
相关标签/搜索