大家好呀~ 今天是数据分析课程 Python 基础知识部分的第四节课,类和对象。从这一课开始,我们要了解 Python 面向对象编程的思想和知识,在此之前我们所学习的 Python 基础知识代码都是面向过程编程。
面向对象编程和面向过程编程这两个概念,对于零基础的同学来说可能太过抽象,不容易理解和区分。我用老师上课举的一个例子来说明一下,你就懂了。
假设你现在要做一道菜,面向过程的方法是,买菜,洗菜,切菜,炒菜等一系列正常做菜的流程。而面向对象的方法是找一个“对象”来给你做这道菜,这个“对象”可以是一位厨师,可以是你男/女朋友,也可以是外卖商家,让他们来为你完成做菜这个任务。
在编程中,我们经常需要实现一些功能、方法或者任务,如果这些功能、方法或任务是需要经常使用的,那么你就应该构造一些能够实现它们的“对象”(也就是函数),每次要用到的时候,直接叫这些对象来执行就行(也就是调用函数)。
接下来,让我们来系统学习一下类和对象的相关知识吧。
本节知识概要:
1、面向对象编程
2、类和对象
(1)类
(2)对象
(3)类和对象之间的关系
(4)定义类和创建对象
3、对象的属性和方法
(1)添加和获取对象的属性
(2)通过self获取对象属性
(3)__init__
魔法方法
4、继承
(1)单继承
(2)多继承
(3)重写父类方法
5、多态
一、面向对象编程
面向对象编程(Object Oriented Programming,简称OOP),是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。
我们以一个例子来说明面向过程和面向对象在程序流程上的不同之处。
假设我们要处理学生的成绩表,为了表示一个学生的成绩,面向过程的程序可以用一个 dict
表示:
std1 = { 'name': 'Curry', 'score': 98 }
std2 = { 'name': 'James', 'score': 81 }
而处理学生成绩可以通过函数实现,比如打印学生的成绩:
def print_score(std):
print('%s: %s' % (std['name'], std['score']))
如果采用面向对象的程序设计思想,我们首选思考的不是程序的执行流程,而是 Student
这种数据类型应该被视为一个对象,这个对象拥有 name
和 score
这两个属性(Property)。如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个 print_score
消息,让对象自己把自己的数据打印出来。
给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法(Method)。面向对象的程序写出来就像这样:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
面向对象的设计思想是从自然界中来的,因为在自然界中,类(Class)和实例(Instance)的概念是很自然的。Class 是一种抽象概念,比如我们定义的 Class——Student
,是指学生这个概念,而实例(Instance)则是一个个具体的 Student
,比如, Bart Simpson 和 Lisa Simpson 是两个具体的 Student
。
所以,面向对象的设计思想是抽象出 Class ,根据 Class 创建 Instance 。
面向对象的抽象程度又比函数要高,因为一个 Class 既包含数据,又包含操作数据的方法。
二、类和对象
类和对象是面向对象编程的两个非常重要的概念。
对象是面向对象编程的核心,在使用对象的过程中,为了将具有共同特征和行为的一组对象抽象定义,提出了另外一个新的概念——类
类就相当于制造飞机时的图纸,用它来进行创建的飞机就相当于对象。
1. 类
类是具有相同属性和行为的事物的统称。类是抽象的,我们在使用的时候通常会找到这个类的一个具体的存在,使用这个具体的存在。一个类可以找到多个对象。
图纸.png2. 对象
飞机.png对象是某一个具体的事物,在现实世界中是可以看得见摸得着的,是可以直接使用的。
3. 类和对象之间的关系
玩具和模型.png总结:类就是创建对象的模板
4. 定义类和创建对象
定义一个类,格式如下:
class 类名:
方法列表
# class Hero: # 经典类(旧式类)定义形式
# class Hero():
class Hero(object): # 新式类定义形式
def info(self):
print("hero")
说明:
- 定义类时有2种形式:新式类和经典类,上面代码中的
Hero
为新式类,前两行注释部分则为经典类; -
object
是 Python 里所有类的最顶级父类; - 类名的命名规则按照"大驼峰命名法";
-
info
是一个实例方法,第一个参数一般是self
,表示实例对象本身,当然可以将self
换为其它的名字,其作用是一个变量,这个变量指向了实例对象。 - python 中,可以根据已经定义的类去创建出一个或多个对象。
创建对象的格式为:
对象名1 = 类名()
对象名2 = 类名()
对象名3 = 类名()
class Hero(object): # 新式类定义形式
"""info 是一个实例方法,类对象可以调用实例方法,实例方法的第一个参数一定是self"""
def info(self):
"""当对象调用实例方法时,Python会自动将对象本身的引用做为参数, 传递到实例方法的第一个参数self里"""
print(self)
print("self各不同,对象是出处。")
# Hero这个类 实例化了一个对象
hero = Hero()
# 对象调用实例方法info(),执行info()里的代码
# . 表示选择属性或者方法
hero.info()
print(hero) # 打印对象,则默认打印对象在内存的地址,结果等同于info里的print(self)
三、对象的属性和方法
1. 添加和获取对象的属性
class Hero(object):
"""定义了一个英雄类,可以移动和攻击"""
def move(self):
"""实例方法"""
print("正在前往事发地点...")
# 实例化了一个英雄对象
hero = Hero()
# 给对象添加属性,以及对应的属性值
hero.name = "德玛西亚" # 姓名
hero.hp = 2600 # 生命值
# 通过.成员选择运算符,获取对象的属性值
print("英雄 %s 的生命值 :%d" % (hero.name, hero.hp))
# 通过.成员选择运算符,获取对象的实例方法
taidamier.move()
2. 通过 self
获取对象属性
class Hero(object):
"""定义了一个英雄类,可以移动和攻击"""
def move(self):
"""实例方法"""
print("正在前往事发地点...")
def info(self):
"""在类的实例方法中,通过self获取该对象的属性"""
print("英雄 %s 的生命值 :%d" % (self.name, self.hp))
# 实例化了一个英雄对象
hero = Hero()
# 给对象添加属性,以及对应的属性值
hero.name = "德玛西亚" # 姓名
hero.hp = 2600 # 生命值
# 通过.成员选择运算符,获取对象的实例方法
hero.info() # 只需要调用实例方法info(),即可获取英雄的属性
hero.move()
3. __init__
魔法方法
(1)__init__
方法
class Hero(object):
"""定义了一个英雄类,可以移动和攻击"""
# Python 的类里提供的,两个下划线开始,两个下划线结束的方法,就是魔法方法,`__init__()` 就是一个魔法方法,通常用来做属性初始化 或 赋值 操作。
# 如果类面没有写 `__init__` 方法,Python 会自动创建,但是不执行任何操作,
# 如果为了能够在完成自己想要的功能,可以自己定义 `__init__` 方法,
# 所以一个类里无论自己是否编写 `__init__` 方法 一定有 `__init__` 方法。
def __init__ (self):
""" 方法,用来做变量初始化或赋值操作,在类实例化对象的时候,会被自动调用"""
self.name = "hero" # 姓名
self.hp = 2600 # 生命值
def move(self): """实例方法"""
print("正在前往事发地点...")
# 实例化了一个英雄对象,并自动调用 `__init__()` 方法
hero = Hero()
# 通过.成员选择运算符,获取对象的实例方法
hero.info() # 只需要调用实例方法 info(),即可获取英雄的属性
hero.move()
总结:
-
__init__()
方法,在创建一个对象时默认被调用,不需要手动调用; -
__init__(self)
中的self
参数,不需要开发者传递,python 解释器会自动把当前的对象引用传递过去。
(2)有参数的 __init__()
方法
class Hero(object):
"""定义了一个英雄类,可以移动和攻击"""
def __init__ (self, name, hp):
""" __init__() 方法,用来做变量初始化 或 赋值 操作""" # 英雄名
self.name = name # 生 命 值 : self.hp = hp
def move(self): """实例方法"""
print("%s 正在前往事发地点..." % self.name)
def info(self):
print("英雄 %s 的生命值 :%d" % (self.name, self.hp))
注意:
- 通过一个类,可以创建多个对象,就好比通过一个模具创建多个实体一样;
-
__init__(self)
中,默认有 1 个参数名字为self
,如果在创建对象时传递了 2 个实参,那么__init__(self)
中除了self
作为第一个形参外还需要 2 个形参,例如__init__(self,x,y)
- 在类内部获取属性和实例方法,通过
self
获取; - 在类外部获取 属性 和 实例方法,通过对象名获取。
- 如果一个类有多个对象,每个对象的属性是各自保存的,都有各自独立的地址;
- 但是实例方法是所有对象共享的,只占用一份内存空间。类会通过self来判断是哪个对象调用了实例方法。
四、继承
- 在程序中,继承描述的是多个类之间的所属关系。
- 如果一个类 A 里面的属性和方法可以复用,则可以通过继承的方式,传递到类 B 里。
- 那么类 A 就是基类,也叫做父类;类 B 就是派生类,也叫做子类。
# 父类
class A(object):
def __init__(self):
self.num = 10
def print_num(self):
print(self.num + 10)
# 子类
class B(A):
pass
b = B()
print(b.num)
b.print_num()
1. 单继承
单继承:子类只继承一个父类。
# 定义一个Person类
class Person(object):
def __init__(self):
# 属性
self.name = "女娲"
# 实例方法
def make_person(self):
print(" <%s> 造了一个人..." % self.name)
# 定义Teacher类,继承了 Person,则Teacher是子类,Person是父类。
class Teacher(Person):
# 子类可以继承父类所有的属性和方法,哪怕子类没有自己的属性和方法,也可以使用父类的属性和方法。
pass
panda = Teacher() # 创建子类实例对象
print(panda.name) # 子类对象可以直接使用父类的属性
damao.make_person() # 子类对象可以直接使用父类的方法
总结:
- 虽然子类没有定义
__init__
方法初始化属性,也没有定义实例方法,但是父类有。所以只要创建子类的对象,就默认执行了那个继承过来的__init__
方法; - 子类在继承的时候,在定义类时,小括号
()
中为父类的名字; - 父类的属性、方法,会被继承给子类;
2. 多继承
多继承:子类继承多个父类。
class Women(object):
def __init__(self):
self.name = "女娲" # 实例变量,属性
def make_person(self): # 实例方法,方法
print(" <%s> 造了一个人..." % self.name)
def move(self):
print("移动..")
class Man(object):
def __init__(self):
self.name = "亚当"
def make_person(self):
print("<%s> 造了一个人..." % self.name)
def run(self):
print("跑..")
class Person(Women, Man): # 多继承,继承了多个父类
pass
ls = Person()
print(ls.name)
ls.make_person()
# 子类的魔法属性 mro 决定了属性和方法的查找顺序
print(Person. mro )
扩展:关于
__mro__
:
python 类有多继承特性,如果继承关系太复杂,很难看出会先调用那个属性或方法。为了方便且快速地看清继承关系和顺序,我们可以用__mro__
方法来获取这个类的调用顺序。
结论:
- 多继承可以继承多个父类,也继承了所有父类的属性和方法;
-
注意:如果多个父类中有同名的属性和方法,则默认使用第一个父类的属性和方法(根据类的魔法属性
mro
的顺序来查找) - 多个父类中,不重名的属性和方法,不会有任何影响。
3. 重写父类方法
子类继承父类,当父类的方法满足不了子类的需要,子类可以对父类的方法进行重写。
重写的特点:
- 继承关系;
- 方法名相同。
class Person(object):
def run(self):
print("跑起来了")
class Student(Person):
def __init__(self, name, age):
self.name = name
self.age = age
# 因为父类的方法满足不了子类的需要,对其进行重写
def run(self):
print("%s跑起来了" % self.name)
stu = Student("王五", 10)
# 调用方法的时候先从本类去找,如果本来没有再去父类去找,会遵循 mro 的特点
stu.run()
4、多态
多态是指,不同的子类对象调用相同的父类方法,产生不同的执行结果,可以增加代码的外部调用灵活度。多态以继承和重写父类方法为前提。
多态是调用方法的技巧,不会影响到类的内部设计。
class Animal(object):
def run(self):
print('Animal is running...')
class Dog(object):
def run(self):
print('Dog is running...')
class Cat(object):
def run(self):
print('Cat is running...')
# 定义一个方法
def run_twice(animal):
animal.run()
animal.run()
dog = Dog()
cat = Cat()
run_twice(dog)
run_twice(cat)
网友评论