9. 初识面向对象 - 1
引入面向对象
本节将通过一个案例,先用之前我们编写的代码方式进行实现,然后分析利弊。从而引出面向对象。
需求
编写一个程序,完成1个学生的基本操作
- 学生有姓名、年龄
- 可以输出学习信息( 姓名、年龄)
使用面向过程的方式实现
# 遍历打印所有姓名-年龄
def print_info(names_temp, ages_temp):
print("姓名:", names_temp, " 年龄:", ages_temp)
# 定义一个变量存储姓名
name = "顾安"
# 定义一个变量存储年龄
age = 20
# 遍历姓名-年龄
print_info(name, age)
分析
从上述通过“面向过程”方式实现的代码中,我们可以发现代码行数不是很多,整个程序比较简单
但是上面的代码虽然看上去简单,实际上如果我们想要进行升级功能则不见得容易,例如:我们需要在上述的代码基础上进行修改实现3名同学的信息存储,那么应该怎么办呢?看看下面的方式是否可行?有什么不好的地方?
# 遍历打印所有姓名-年龄
def print_info(names_temp, ages_temp):
print("姓名:", names_temp, " 年龄:", ages_temp)
# 定义第1个人的信息,然后输出
name = "顾安"
age = 20
print_info(name, age)
# 定义第2个人的信息,然后输出
name = "双双"
age = 22
print_info(name, age)
# 定义第3个人的信息,然后输出
name = "安娜"
age = 23
print_info(name, age)
发现:为了实现存储3个学生信息,就用了3组变量 每组存储姓名、年龄,代码已经产生了较大冗余,如果要是存储30个学生信息想必这种方式肯定不好,代码太太太冗余
下面对代码进行优化:
# 遍历打印所有姓名-年龄
def print_info(names_temp, ages_temp):
print("姓名:", names_temp, " 年龄:", ages_temp)
# 定义第1个人的信息,然后输出
names = ["顾安", "双双", "安娜"]
ages = [20, 22, 23]
print_info(names[0], ages[0])
print_info(names[1], ages[1])
print_info(names[2], ages[2])
上面我们使用了列表
进行了升级,避免了定义多个变量的问题
你是否有这样的感觉,即便是升级了总感觉还是不够好
如果有这种感觉就对了,因为上面的这种方式就是“面向过程”开发,这种开发模式就是一步步的对需要的数据
以及方法
进行操作
因此出现代码“乱”的情况,在所难免。
使用面向对象的方式实现
下面代码是使用面向对象开发模式,实现的1名同学信息的代码
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# 遍历打印所有姓名-年龄
def print_info(self):
print("姓名:", self.name, " 年龄:", self.age)
# 创建一个对象
p = Person("顾安", 20)
p.print_info()
上述代码,第12行定义了一个Person
类的变量p
让这它指向一个对象,在这个对象中有name
、age
以及打印信息的功能代码(本节仅仅是引出面向对象的好处,详细的对象等知识接下来详细讲解),这样一来想要表示一个人就创建一个Person
类的对象即可,如果不看第1~10
行,只看第12~13
行的话,就会有一种感觉“原来模拟创建一个人只需要这么简单的代码”
按照这个思路,如果想要创建3个人呢?见如下代码:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# 遍历打印所有姓名-年龄
def print_info(self):
print("姓名:", self.name, " 年龄:", self.age)
# 创建一个对象
p = Person("顾安", 20)
p.print_info()
# 创建另外2个对象
p2 = Person("双双", 22)
p2.print_info()
p3 = Person("安娜", 23)
p3.print_info()
面向过程与面向对象的对比分析
通过上述2种代码的实现方式(面向过程、面向对象)我们能够的粗以下几个结论:
- 面向过程:根据业务逻辑从上到下写代码
- 面向对象:将数据与函数绑定到一起,进行封装,这样能够更快速的开发程序,减少了重复代码的重写过程
- 面向过程编程最易被初学者接受,其往往用一长段代码来实现指定功能,开发过程的思路是将数据与函数按照执行的逻辑顺序组织在一起,数据与函数分开考虑。
简单总结
面向对象编程(Object Oriented Programming
-OOP
) 是一种解决软件复用的设计和编程方法。
这种方法把软件系统中相近相似的操作逻辑和操作 应用数据、状态,以类的型式描述出来,以对象实例的形式在软件系统中复用,以达到提高软件开发效率的作用。
大白话来一句:面向对象能实现的功能,面向过程也能实现,往往程序的代码量都比较大,如果用面向过程的方式实现则代码冗余且不已升级,使用面向对象将数据与功能进行封装在中大程序开发中首先考虑使用
类和对象
面向对象编程的2个非常重要的概念:类和对象
对象是面向对象编程的核心,在使用对象的过程中,为了将具有共同特征和行为的一组对象抽象定义,提出了另外一个新的概念——类
- 类,就相当于制造飞机时的图纸,想象成模具
- 对象,就相当于创建的飞机,想象成通过模具制造出的物品
相关概念
类
人以类聚,物以群分。具有相似内部状态和运动规律的实体的集合(或统称为抽象
)。 具有相同属性和行为事物的统称
类是抽象的,在使用的时候通常会找到这个类的一个具体的存在,使用这个具体的存在。一个类可以找到多个对象。
示例:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# 遍历打印所有姓名-年龄
def print_info(self):
print("姓名:", self.name, " 年龄:", self.age)
对象
某一个具体事物的存在 ,在现实世界中可以是看得见摸得着的。可以是直接使用的
示例:
Person("安娜", 20) # 执行完之后返回值就是对象的引用
类与对象的关系
先有类,通过类创建对象
用一个生活中的例子来说明:某公司在签署某个合同时往往需要在文件中盖章,在纸质合同上的印章图案就是对象,那么印章就是类。
一句话说明:类是模板,对象是类这个模板生产出来的具体的产品。
类的构成
类class
由三个部分组成:
- 类的
名称
:类名 - 类的
属性
:一组数据 - 类的
方法
:允许对进行操作的方法 (行为)
示例:
- 如何创建人物对象
- 事物名称(类名):人(Person)
- 属性:身高(height)、年龄(age)
- 方法(行为/功能):跑(run)、打架(fight)
- 如何创建狗类对象
- 类名:狗(Dog)
- 属性:品种 、毛色、性别、名字、 腿儿的数量
- 方法(行为/功能):叫 、跑、咬人、吃、摇尾巴
定义类与创建对象
定义类
格式如下:
class 类名:
方法列表
示例:
# class Hero: # 经典类(旧式类)定义形式
# class Hero():
class Hero(object): # 新式类定义形式
# 方法
def info(self):
print("英雄各有见,何必问出处。")
说明:
- 定义类时有2种形式:新式类和经典类,上面代码中的
Hero
为新式类,前两行注释部分则为经典类; object
是Python
里所有类的最顶级父类;- 类名的命名规则按照
大驼峰命名法
; info
是一个实例方法,第一个参数一般是self
,表示实例对象本身,当然了可以将self
换为其它的名字,其作用是一个变量 这个变量指向了实例对象
创建对象
Python
中,可以根据已经定义的类去创建出一个或多个对象
创建对象的格式为:
变量名 = 类名()
示例:
class Hero(object): # 新式类定义形式
"""
info 是一个实例方法,类对象可以调用实例方法,实例方法的第一个参数一定是self
"""
def info(self):
"""当对象调用实例方法时,Python会自动将对象本身的引用做为参数,
传递到实例方法的第一个参数self里"""
print(self)
print("self各不同,对象是出处。")
# 创建一个对象
h1 = Hero()
# 对象调用实例方法info(),执行info()里的代码
# . 表示选择属性或者方法
h1.info()
print(h1) # 打印对象,则默认打印对象在内存的地址
print(id(h1)) # id(h1) 则是内存地址的十进制形式表示
简单总结
- 先要用
class
定义类,然后再创建对象 - 为了便于以后学习类对象时进行区分对象,我们把上述代码创建出来的对象称之为
实例对象
实例属性与实例方法
实例方法的概念
我们把创建出来的对象称之为:实例对象
,简称对象
所以当我们说实例属性
时,就是指这个对象的属性
那什么是方法呢?
通俗的说:方法
就是
对象中定义的函数
,用它来实现功能
实例方法的定义
定义实例方法的方式与定义函数很类似,唯独不同点在于形参中的第一个参数是self
代码示例:
class Hero(object):
def test1(self):
print("这是一个方法")
def test2(self, age):
print("age=%d" % age)
def test3(self, score1, socre2, socre3):
return (score1 + socre2 + socre3) / 3
实例方法的调用
函数的调用方式:
函数名(实参)
实例方法的调用
实例对象.实例方法名(实参)
示例一:
class Cat(object):
# 实例方法
def eat(self):
print("猫在吃鱼....")
def drink(self):
print("猫在喝可乐...")
# 创建了一个对象
tom = Cat()
tom.eat() # 调用对象的eat方法
tom.drink()
示例二:
class Hero(object):
def test1(self):
print("这是一个方法")
def test2(self, age):
print("age=%d" % age)
def test3(self, socre1, socre2, socre3):
return (socre1 + socre2 + socre3) / 3
# 创建Hero类的对象
hero = Hero()
hero.test1() # test1无参无返回值
hero.test2(19) # test2需要传递实参
average_score = hero.test3(100, 99, 98) # test3需要实参,有返回值因此需要用变量来存储
注意:
- 实例方法的调用,一定要用
实例对象.方法名()
调用
实例属性的概念
什么是实例属性呢?
通俗的说:实例属性
就是对象中
可以用的变量
,用它来存储数据
定义与使用
定义方式:
方式一
对象名.属性名
示例:
class Hero(object):
pass
# 创建对象
hero = Hero()
# 给对象设置属性(通俗的说就是:给hero标记的实例对象添加变量且赋值)
hero.name = "安娜"
hero.age = 18
hero.address = "长沙"
方式二:
class 类名(object):
def 方法名(self):
self.实例属性名 = 数据
示例:
class Hero(object):
def set_info(self):
# 下面定义的了3个实例属性,且给它们设置了初始值
self.name = "安娜"
self.age = 18
self.address = "长沙"
# 创建实例对象
hero = Hero()
# 调用方法,从而让第4、5、6行被执行,从而完成实例对象的属性添加
hero.set_info()
注意:
- 这种方式定义的实例属性,需要调用它所在的方法才行,如果方法不调用那么定义的属性就不会被执行,因此对象就不会有这些属性
更加灵活的使用属性
虽然定义与使用属性有上述2种方式,但是实际开发工作中,是互相用的,没有人规定我们必须用哪一种,只要能实现需求即可
示例:
class Hero(object):
def set_info(self):
# 下面定义的了3个实例属性,且给它们设置了初始值
self.name = "安娜"
self.age = 18
self.address = "长沙"
def print_info(self):
print(self.qq, self.email)
# 创建实例对象
hero = Hero()
# 调用方法,从而让第4、5、6行被执行,从而完成实例对象的属性添加
hero.set_info()
# 通过对象方式直接获取属性
print(hero.name, hero.age, hero.address)
# 给对象添加额外的属性
hero.qq = 123456
hero.email = "[email protected]"
# 调用方法,在方法中获取qq、email
hero.print_info()
说明:
- 虽然上述的代码中展示了我们使用属性的灵活性,但也总感觉代码有些凌乱,所以后面我们将属性的定义都放到
__init__
方法中
简单总结
- 用类创建出来的对象,我们一般称之为“实例对象”
- 实例方法,就是对象中的函数,实现了功能
- 实例属性,就是对象中的变量,实现了存储数据
类中的self
理解给对象添加属性
class Hero(object):
def set_info(self):
# 下面定义的了3个实例属性,且给它们设置了初始值
self.name = "安娜"
self.age = 18
self.address = "长沙"
def print_info(self):
print(self.qq, self.email)
# 创建实例对象
hero = Hero()
# 调用方法,从而让第4、5、6行被执行,从而完成实例对象的属性添加
hero.set_info()
# 通过对象方式直接获取属性
print(hero.name, hero.age, hero.address)
# 给对象添加额外的属性
hero.qq = 123456
hero.email = "[email protected]"
# 调用方法,在方法中获取qq、email
hero.print_info() # 这句话等同于print_info(hreo)
说明:
- 创建对象后,变量
hero
指向了一个空间,这个空间可以理解为对象空间 - 对象中可以存放添加了所有的属性
实例方法获取实例属性
当类名 + ()
表示对类进行了实例化,会在内存中存储类中的代码,并把存储的地址赋值给hreo
这个变量,当前这个变量我们也称作实例对象。当使用实例对象.实例方法()
时,当前方法会在内存中寻找之前定义的属性并打印。
实例对象.实例方法()
就相当于实例方法(实例对象)
- 定义实例方法的时候,之所以第一个形参一定是
self
就是因为,当我们调用实例方法的时候Python解释器会自动将这个对象的引用当做第一个实参
self
的作用
通过上述的学习,大家能够看出,self
的作用就是当通过实例对象调用方法的时候self
能够自动指向实例对象,从而拥有了操作这个对象中的属性或者方法的可能。
实例:
class Cat(object):
def set_info(self, new_name, new_age):
self.name = new_name
self.age = new_age
def eat(self):
print("%s在吃鱼...." % self.name)
def drink(self):
print("%s在喝可乐..." % self.name)
def print_info(self):
print("名字是:%s, 年龄是:%d" % (self.name, self.age))
# 创建对象
tom = Cat()
# 调用方法,设置属性
tom.set_info("汤姆猫", 30)
# 调用实例方法
tom.eat()
tom.drink()
tom.print_info()
代码说明:
Cat()
创建了实例对象并把这个对象的地址赋值给了tom
tom.set_info('汤姆猫', 30)
相当于使用set_info()
方法在指定的内存中创建了两个属性
案例:多个实例对象
class Cat(object):
def set_info(self, new_name, new_age):
self.name = new_name
self.age = new_age
def eat(self):
print("%s在吃鱼...." % self.name)
def drink(self):
print("%s在喝可乐..." % self.name)
def print_info(self):
print("名字是:%s, 年龄是:%d" % (self.name, self.age))
# 创建对象tom
tom = Cat()
# 调用方法,设置属性
tom.set_info("汤姆猫", 30)
# 调用实例方法
tom.eat()
tom.drink()
tom.print_info()
# 创建对象jia_fei
jia_fei = Cat()
# 调用方法,设置属性
jia_fei.set_info("加菲猫", 18)
# 调用实例方法
jia_fei.eat()
jia_fei.drink()
jia_fei.print_info()
代码说明:
Cat()
创建了实例对象并把这个对象的地址赋值给了tom
tom.set_info('汤姆猫', 30)
相当于使用set_info()
方法在指定的内存中创建了两个属性- 在创建
tom
后由再次创建了jia_fei
,并且jia_fei
这个变量存储了一个新的对象的引用地址 - 使用
jia_fei
调用类中的相关方法与属性
简单总结
- 所谓的
self
,可以理解为自己,谁调用方法中的self就指向谁
- 可以把
self
当做C++编程语言
中类里面的this
指针一样理解,就是对象自身的意思 - 某个对象调用其方法时,
python
解释器会把这个对象作为第一个参数传递给self
,所以开发者只需要传递后面的参数即可 self
仅仅是一个变量名,也可将self
换为其他任意的名字,但是为了能够让其他开发人员能明白这变量的意思,因此一般都会self当做名字
__init__
方法
引入
回顾上一节学习self
时代码,如下:
class Cat(object):
def set_info(self, new_name, new_age):
self.name = new_name
self.age = new_age
def eat(self):
print("%s在吃鱼...." % self.name)
def drink(self):
print("%s在喝可乐..." % self.name)
def print_info(self):
print("名字是:%s, 年龄是:%d" % (self.name, self.age))
# 创建对象tom
tom = Cat()
# 调用方法,设置属性
tom.set_info("汤姆猫", 30)
# 调用实例方法
tom.eat()
tom.drink()
tom.print_info()
# 创建对象jia_fei
jia_fei = Cat()
# 调用方法,设置属性
jia_fei.set_info("加菲猫", 18)
# 调用实例方法
jia_fei.eat()
jia_fei.drink()
jia_fei.print_info()
通过上述的代码,我们发现想要让对象拥有name
、age
属性,就必须要调用set_info
方法,那是否可以更加简单点呢?毕竟我们知道定义变量时,可以顺便给它默认值;那创建对象时能否让它默认执行某个方法完成属性的设置呢?
答:可以
__init__
创建对象后,Python会自动调用一个特殊的方法名字叫__init__
,一般情况下我们会在这个方法中完成对象属性的设置
定义示例如下:
class 类名(object):
def __init__(self):
pass
注意,__init__
方法会在对象创建完毕后,自动调用,无需我们自己调用
class Cat(object):
def __init__(self):
print("我是__init__方法")
cat = Cat() # 此时就会自动调用
创建对象的过程
注意,一般情况下我们会在这个方法中完成对象属性的设置,示例如下:
class Cat(object):
def __init__(self, new_name, new_age):
self.name = new_name
self.age = new_age
def eat(self):
print("%s在吃鱼...." % self.name)
def drink(self):
print("%s在喝可乐..." % self.name)
def print_info(self):
print("名字是:%s, 年龄是:%d" % (self.name, self.age))
# 创建对象tom
tom = Cat("汤姆猫", 30)
# 调用实例方法
tom.eat()
tom.drink()
tom.print_info()
说明
__init__()
方法,在创建一个对象时默认被调用,不需要手动调用__init__(self)
中的self参数,不需要开发者传递,python解释器会自动把当前的对象引用传递过去
上述代码执行过程:
Cat("汤姆猫", 30)
创建对象并把对象的内存地址赋值给tom
- 当对象创建时自动调用
__init__
方法,当前方法中的self
指向tom
这个变量所存储的地址,并使用self
创建属性 - 因为
tom
这个变量与self
指向的是同一个地址,所以tom
也具有了name
和age
这两个属性
__init__
案例
案例一:
class Hero(object):
"""定义了一个英雄类,可以移动和攻击"""
def __init__(self, new_name, new_hp, new_atk, new_armor):
self.name = new_name # 姓名
self.hp = new_hp # 生命值
self.atk = new_atk # 攻击力
self.armor = new_armor # 护甲值
def move(self):
print("正在前往事发地点...")
def attack(self):
print("发出了一招强力的普通攻击...")
# 实例化了一个英雄对象,并自动调用__init__()方法
taidamier = Hero("程咬金", 1900, 900, 800)
# 通过.成员选择运算符,获取对象的实例方法
taidamier.info() # 只需要调用实例方法info(),即可获取英雄的属性
taidamier.move()
taidamier.attack()
案例二:
class Hero(object):
"""定义了一个英雄类,可以移动和攻击"""
def __init__(self, new_name, new_skill, new_hp, new_atk, new_armor):
self.name = new_name
self.skill = new_skill
self.hp = new_hp
self.atk = new_atk
self.armor = new_armor
def move(self):
"""实例方法"""
print("%s 正在前往事发地点..." % self.name)
def attack(self):
"""实例方法"""
print("发出了一招强力的%s..." % self.skill)
def info(self):
print("英雄 %s 的生命值 :%d" % (self.name, self.hp))
print("英雄 %s 的攻击力 :%d" % (self.name, self.atk))
print("英雄 %s 的护甲值 :%d" % (self.name, self.armor))
# 实例化英雄对象时,参数会传递到对象的__init__()方法里
taidamier = Hero("泰达米尔", "旋风斩", 2600, 450, 200)
gailun = Hero("盖伦", "大宝剑", 4200, 260, 400)
# 调用对象方法
taidamier.attack()
taidamier.move()
gailun.move()
说明:
- 通过一个类,可以创建多个对象,就好比 通过一个模具创建多个实体一样
__init__(self)
中,默认有1个参数名字为self,如果在创建对象时传递了2个实参,那么__init__(self)
中出了self作为第一个形参外还需要2个形参,例如__init__(self,x,y)
注意点:
- 在类内部获取 属性 和 实例方法,通过
self
获取 - 在类外部获取 属性 和 实例方法,通过对象名获取
- 如果一个类有多个对象,每个对象的属性是各自保存的,都有各自独立的地址
- 但是实例方法是所有对象共享的,只占用一份内存空间。类会通过
self
来判断是哪个对象调用了实例方法