Python 基础四

面向对象、类、继承、多态

知识点

1、面向对象编程

面向对象编程–Object Oriented Programming,简称 OOP,是一种程序设计思想。OOP 把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。

面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的有序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把一大块函数通过切割成小块函数来降低系统的复杂度。

而面向对象的程序设计把计算机程序视为一组对象集合,而每个对象都可以接受其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一些列消息在各个对象之间的传递。

Python中所有的数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class) 的概念。

一个例子来说明面向过程和面向对象在程序流程上的不同之处。

假设我们要处理学生的成绩表,为了表示一个学生的成绩,面向过程的程序可以用一个 dict 表示:

1
2
std1 = {'name':'Curry','score':98}
std1 = {'name':'James','score':81}

而处理学生成绩可以通过函数实现,比如打印学生的成绩:

1
2
def print_score(std):
print('%s: %s' % (std['name'],std['score']))

如果采用面向对象的程序设计思想,我们首先思考的不是程序的执行流程,而是 Student 这种数据类型应该被视为一个对象,这个对象拥有 name 和 score 这个属性(Property)。如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个 print_score 的消息,让对象自己把自己的数据打印出来。

1
2
3
4
5
6
7
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))

给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法(Method) 。面向对象的程序写出来就这样:

1
2
3
4
bart = Student('Bart Simpson',59)
lisa = Srudent('Lisa Simpson',87)
bart.print_score()
lisa.print_score()

面向对象的设计思想是从自然界中来的,因为在自然界中,类(Class)和实例(instance)的概念是很自然的。Class 是一种抽象概念,比如我们定义的 class–Student ,是指学生这个概念,而实例(instance)则是一个个具体的 student,例如,Bart Simpson 和 Lisa Simpson是两个具体的 Student。

所以,面向对象的设计思想是抽象出来的 Class,根据Class 创建 instance。

面向对象的抽象程度又比函数要高,因为一个Class 既包含数据,又包含操作数据的方法。

2、类和对象

面向对象编程的两个重要概念:类和对象

对象是面向对象编程的核心,在使用对象的过程中,为了将具有共同特征和行为的一组对象抽象定义,提出了另外一个新的概念–类

类就相当于制造飞机时的图纸,用它来进行创建的飞机就相当于对象。

1、类

人以物聚,物以群分。具有相似内部状态和运动规律的实体的集合(或统称为抽象)。具有相同的属性和行为事物的统称 类是抽象的,在使用的时候通常会找到这个类的一个具体的存在,使用这个具体的存在。一个类可以找到多个对象

2、对象

某个具体失误的存在,在现实世界中可以是看得见摸得着的。可以直接使用的

3、类和对象之间的关系

总结:类就就是创建对象的模板

4、定义类和创建对象

定义一个类,格式如下:

1
2
3
4
5
6
7
class 类名:
方法列表
# class Hero: # 经典类(旧式类)定义形式
# class Hero():
class Hero(): # 新式类定义形式
def info(self):
print("hello")

说明:

  • 定义类时有两种形式:新式类和经典类,上面代码中Hero为新式类,前两行注释部分为经典类;
  • object 是 python 里所有类的最顶级父类:
  • 类名 的命名规则按照“大驼峰命名法”
  • info 是一个实例方法,第一个参数一般为 self,表示实例对象本身,当然了可以将self 换成为其他的名字,其作用是一个变量 这个变量指向了实例对象
  • python 中,可以根据已定义的类去创造出一个或多个类

创建对象的格式为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
对象名1 = 类名()
对象名2 = 类名()
对象名3 = 类名()
class Hero(object): # 新式类定义形式
"""
info 是一个实例方法,类对象可以调用的实例方法,实例方法的第一参数一定是 self
"""
def info(self):
"""
当对象调用实例方法时,python会自动将对象本身的引用作为参数,传递到实例方法的第一个参数
"""
print(self)
print('self各不相同')

# Hero 这个类 实例化一个对象
hero = Hero()

# 对象调用实例方法 info(),执行 info()里的代码
# . 表示选择属性或方法
hero.info()
print(hero) # 打印对象,则默认打印对象在内存的地址,结果等同于 info 里的 print(self)

3、对象的属性和方法

1、添加和获取对象的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 获取对象属

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 魔法方法

init方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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解释器会自动把当前对象引用传递过去

有参数的init()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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__()方法里
blind = Hero("瞎子"2600)
gailun = Hero('盖伦'4200)

# print(gailun)
# print(blind)

# 不同对象的属性值得单独保存
print(id(blind.name()))
print(id(gailun.name()))

# 同一个类的不同对象实例方法共享
print(id(blind.move()))
print(id(gailun.move()))

注意:

  • 通过一个类,可以创建多个对象,好比一个模具可以创建多个实体
  • _init_(self) 中,默认有一个参数名字为self,如果再创建对象时传递了2个实参,那么_init_(self) 中除了self 作为第一个形参外还需要两个形参,例如:_init_(self,x,y)
  • 在类内部获取 属性 和实例方法,通过self 获取;
  • 在类外部获取 属性 和 实例方法,通过对象名获取;
  • 如果一个类有多个对象,内个对象的属性是各自保存的,都有各自独立的地址;
  • 但是实例方法时所有对象共享的,只占一份内存。类会通过self来判断是哪个对象调用了实例方法。

4、继承

  • 在程序中,继承描述的是过个类之间的所属关系。
  • 如果一个类A里面的属性和方法可以重复使用,则可以通过继承的方法传递到类B里
  • 那么类A就是 基类,也叫父类;类B就是派生类,也叫子类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 父类
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、单继承

单继承:子类只继承父类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 定义一个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、多继承

多继承: 子类继承多个父类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Woman(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(Woman,Man): # 多继承,集成多个父类
pass

ls = Person()
print(ls.name)
ls.make_person()

# 子类的魔法属性 __mro__ 决定了属性和方法的查找顺序
Print(Person.__mro__)

结论:

  • 多继承可以继承多个父类,也继承了所有父类的属性
  • 注意: 如果多个父类中有同名的 属性和方法,则默认使用第一个父类的属性和方法(根据类的魔法属性 mro 的顺序查找)
  • 多父类中,不重名的属性和方法不会有任何影响

3、重写父类的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 重写:子类继承父类,父类的方法满足不了子类的需要可以对父类的方法进行重写
# 重写的特点:1、继承关系,1、方法名相同

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、属性方法

1、类属性和实例属性

类属性 就是类对象所拥有的属性,它被所有类对象的实例对象所共有,在内次你中组存在一个副本,这个和C++ 中类的静态成员变量有点类似,对于共有的类属性,在类外可以通过类对象和实例对象访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Person(object):
name = 'Tom' # 公有类属性
__age__ = 12 # 私有类属性

p = People()
print(p.name) # 正确
print(people.name) # 正确
print(p.__age) # 错误,不能在类外通过实例对象访问私有的类属性
print(people.__age) # 错误,不能再类外通过类对象访问私有类属性
实例属性(对象属性)
class People(object):
address = '山东' # 类属性
def __init__(self):
self.name = "xiaowang" # 实例属性
self.age = 20 # 实例属性

p = People()
p.age = 12 # 实例属性
print(p.address) # 正确
print(p.name) # 正确
print(p.age) # 正确

print(People.address) # 正确
print(People.name) # 错误
print(People.age) # 错误

# 通过实例(对象)去修改类属性
class People(object):
country = "china" # 类属性

print(People.country)
p = People()
print(p.country)
p.country = 'japan'
print(p.country) # 实例属性会屏蔽掉同名类属性
print(People.country)
del .p.country # 删除实例属性
print(p.country)

总结

如果需要在类外修改类属性,必须通过类对象去引用然后进行修改,如果通过实例对象去引用,会产生一个同名的实例属性,这种方式修改是实例属性,不会影响到类属性,并且之后如果通过实例对对象去引用该名称属性,实例属性会强制屏蔽掉类属性,即引用的是实例属性,除非删除掉该实例属性。

2、静态方法和类方法

(1) 类方法

是类对象所拥有的方法,需要用修饰器@classmethod 来表示其为类方法,对于类方法,第一个参数必须是类对象,一般以cls作为第一个参数(当然可以用其他名称的变量作为其第一个参数,但是大部分人都习惯一cls作为第一个参数的名字,最好用cls)能够通过实例对象和类对象去访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class People(object):
country ='china'

# 类方法,用classmethod 来进行修饰
@classmethod
def get_country(cls):
return cls.country

p = People()
print(p.get_country()) # 可以通过实例对象引用
print(Pelple.get_country()) # 可以通过类对象引用
类方法还有一个用途 就是可以对类属性进行修改:

class People(object):
country = 'china'

# 类方法,用classmethod来进行修饰
@classmethod
def get_country(cls):
return cls.country
@classmethod
def set_country(cls,country):
cls.country = country

p = People()
print(p.get_country()) # 可以通过实例对象访问
print(People.get_country()) # 可以通过类访问

p.set_country('japan')
print(p.get_country)
print(People.get_country())

# 结果显示在用类方法对类属性修改后,通过类对象和实例对象访问都发生了改变

(2) 静态方法

需要通过修饰其 @classmethod 来进行修饰,静态方法不需要多定义参数,可以通过对象和类来访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class People(object):
country = 'china'

@staticmethod
# 静态方法
def get_country():
return People.country

p = People()
# 通过对象访问静态方法
p.get_country()

# 通过类访问静态方法
print(People.get_country())

总结:

从类方法和实例以及静态方法的定义形式可以看出来,类方法的第一个参数时类对象cls,那么通过cls 引用的必定是类对象的属性和方法;实例方法的第一个参数时实例对象self,那么通过self引用的可能是类属性、也可能是实例属性(这个需要分析),不过在存在相同名称的雷属性和实力属性的情况下,实例属性优先级更高。静态方法中不需要额外定义参数,因此在静态方法中引用雷属性的话,必须通过类实例来引用。

5、多态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
'''
多态,不同的 子类对象调用 相同的 父类方法,产生不同的执行结果,可以增加代买的外部,调用灵活
多态以 继承和重写父类方法为前提
多态是调用方法的技巧,不会影响带类的内部设计
'''

class Animal(object):
def run(self):
print("Animal is running...")

class Dog(object):

def run(self):
print('Dog is running...')

class Cat(self):

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)
感谢您的阅读,本文由 LEE 版权所有。如若转载,请注明出处:LEE(https://ChubbyLEE-Math.github.io/2020/07/07/python%20%E5%9F%BA%E7%A1%80%E5%9B%9B/
Python 基础三
Python 基础五