0%

Python面向对象(三)

#4 Python面向对象(三)

前两节讲解了Python面向对象的思想和Python类中各种变量的含义以及区别。肯定有小伙伴会问,类初始化时是否可以传入参数?如果有多个类中含有共同的函数方法,是否可以重复利用?本节就带着这些问题来继续深入类。Here We Go!

一、类的传参

带参数的初始化

还是以Doctor类为例,假如lisi是一位男性医生,现在要求在生成lisi这个对象时传入其性别。怎么办呢?

按照之前所学的,类似于函数的传参,肯定是:

1
lisi = Doctor('male')

的确,类带参数的初始化就是这么用的,那么再按照函数的思想,类源代码应该这么写:

1
2
3
4
5
6
7
8
9
10
class Doctor(name):
def talk(self):
print('My gender is {0}'.format(self.name))


lisi = Doctor('male')

lisi.talk()
# 按照函数的思想,创建一个带参数的类
# 这是错误的!!!!

你已经看到,代码里已经标出这是错误的编写方法,不信的话来运行一下:

1
2
3
4
5
6
7
Traceback (most recent call last):
File "1.py", line 1, in <module>
class Doctor(name):
NameError: name 'name' is not defined


# 竟然抛出name未定义异常

这是为什么呢?莫名其妙的竟然抛出未定义异常,关于这个问题将会放到下面(类的性质)来讲

那带参数的类要怎么编写呢?使用特殊方法 __init__(注意:init左右两边都是两个下划线) ,先来看个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Doctor():
def __init__(self, name):
¦ self.name = name

def talk(self):
¦ print('My gender is {0}'.format(self.name))


lisi = Doctor('male')

lisi.talk()

# 运行结果:
My gender is male

通过上面的代码可以看出,要想创建一个带参数的类,类里面要创建一个__init__的方法,其实这个特殊的函数叫做构造函数,构造函数会在实例化类后默默运行一次,也就是说,只要实例化了一个类,那么就已经运行了构造函数。构造函数通常用来传入参数使用。以上就是类的传参。

类实例化后运行顺序

当一个类实例化后,类中的代码被首先运行,其次是构造函数里的代码,再然后是被调用的函数被运行,最后是析构函数被运行(析构函数将放在类的特殊方法讲)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Doctor():
def __init__(self, name):
¦ print('我是第二个被运行的')
¦ self.name = name

print('先运行我')

def talk(self):
¦ print('只有调用我时才运行我')


lisi = Doctor('male')

lisi.talk()


# 运行结果:
先运行我
我是第二个被运行的
只有调用我时才运行我

从上面的代码中可以清晰的看到每个代码块的运行顺序

二、面向对象的性质

面向对象共有三大性质:封装性、继承性、多态性。这三大性质是一定要记住的,不仅要记住,更要理解它们👹

封装性

先举个栗子🌰哇,一所学校有教学楼、图书馆、宿舍楼,你作为这个学校的学生,你当然可以随意使用这三个地点,但是外来人员恐怕就不能使用了,学校阻止外来人员的办法是建立围墙,将学校围起来。这就是封装的含义,建立围墙围学校(类)就是封装,只有学生(类的对象)可以使用学校资源,这两点加起来就是封装性。

封装性概念:把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。对内部数据进行了保护,防止程序其他部分使用或修改内部数据

总的来说,封装性就是安全+自私🤭

继承性

还记得前言中带的问题吗?当多个类拥有同样的方法时,是否可以只写一次重复利用,这就是继承的优势。

继承继承,顾名思义,就是继承🤥,父亲和儿子嘛

继承性:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。

继承者称为:子类、派生类;被继承者称为:基类、父类、超类;在Python中,被继承者更习惯称为超类

说了这么多枯燥难以理解的概念,其实只需要一些例子就够了:

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 Animal:
'''
所有动物的超类
'''
def eat(self):
¦ print('动物都会吃饭')

def talk(self):
¦ print('动物都会叫')


class Dog(Animal):
'''
继承Animal
'''
def smell(self):
¦ print('狗的嗅觉灵敏')


class Cat(Animal):
'''
继承Animal
'''
def tree(self):
¦ print('猫会爬树')

从上面的代码可以看到,原来 类(超类) 括号里面时超类啊,不是指参数。调用的例子:

1
2
3
4
5
6
7
8
9
10
dog = Dog()  # 实例化dog
cat = Cat() # 实例化cat

dog.eat() # dog调用了超类Animal的方法
dog.talk()
dog.smell() # dog调用自己的smell方法

cat.eat() # cat也是一样的
cat.talk()
cat.tree()

1
2
3
4
5
6
7
# 运行结果:
动物都会吃饭
动物都会叫
狗的嗅觉灵敏
动物都会吃饭
动物都会叫
猫会爬树

通过继承,可以大大增加代码的利用率。

人类社会中,继承往往是长江后浪推前浪,一浪更比一浪强!Python中的子类当然也可以将超类拍死在沙滩上🥴,那就是改写超类方法方便自己

继承性——改写不带参数的超类

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
class Animal:
'''
所有动物的超类
'''

def eat(self):
¦ print('动物都会吃饭')

def talk(self):
¦ print('动物都会叫')


class Dog(Animal):
'''
继承Animal
'''

def smell(self):
¦ print('狗的嗅觉灵敏')

def eat(self):
¦ '''
¦ 改写超类eat方法
¦ '''
¦ print('吃肉!')


dog = Dog() # 实例化dog

dog.talk()
dog.smell() # dog调用自己的smell方法
dog.eat() # 调用被改写的eat方法
1
2
3
4
# 运行结果:
动物都会叫
狗的嗅觉灵敏
吃肉!

继承性——改写带参数的超类

超类带参数,子类不带参数

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
39
 '''
所有动物的超类
'''

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

def eat(self):
¦ print('动物都会吃饭')

def talk(self):
¦ print('动物都会叫')


class Dog(Animal):
'''
继承Animal
'''

def smell(self):
¦ print('狗的嗅觉灵敏')

def eat(self):
¦ '''
¦ 改写超类eat方法
¦ '''
¦ print('吃肉!')


dog = Dog() # 实例化dog

dog.eat()

# 运行结果:
Traceback (most recent call last):
File "6.py", line 32, in <module>
dog = Dog() # 实例化dog
TypeError: __init__() missing 2 required positional arguments: 'name' and 'age'

运行上述代码,会抛出name,age未定义异常,也就是说,当超类带有参数时,子类实例化时也要参入超类的参数:

1
2
3
4
5
6
7
dog = Dog('wangwang',5)  # 实例化dog,传入超类需要的参数

dog.eat()


# 运行结果:
吃肉!

超类带参数,子类带参数

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
class Animal:
'''
所有动物的超类
'''

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

def eat(self):
¦ print('动物都会吃饭')

def talk(self):
¦ print('动物都会叫')


class Dog(Animal):
'''
继承Animal
'''

def __init__(self, gender):
¦ self.gender = gender

def smell(self):
¦ print('狗的嗅觉灵敏')

def eat(self):
¦ '''
¦ 改写超类eat方法
¦ '''
¦ print('吃肉!')

看到这里,你肯定会有疑问🤔️,传入几个参数才会正确呢?超类需要两个,子类需要一个,那就是三个喽~~~(天真🙂)

1
2
3
4
5
6
7
8
9
10
dog = Dog('wangwang',5, 'male')  # 实例化dog

dog.eat()


# 运行结果:
Traceback (most recent call last):
File "7.py", line 35, in <module>
dog = Dog('wangwang',5, 'male') # 实例化dog
TypeError: __init__() takes 2 positional arguments but 4 were given

根据异常信息可知,init()需要两个参数,但是给了4个。

?????哪来的4个?????明明只传入了3个

不管几个了,既然需要两个参数,那就是只用传入超类的参数啦~~~(天真🙂)

1
2
3
4
5
6
7
8
9
10
dog = Dog('wangwang',5)  # 实例化dog

dog.eat()


# 运行结果:
Traceback (most recent call last):
File "7.py", line 35, in <module>
dog = Dog('wangwang',5) # 实例化dog
TypeError: __init__() takes 2 positional arguments but 3 were given

根据异常信息可知,init()需要2个参数,但是给了3个。

?????怎么又成3个了?????

难不成传入1个会变成2个?岂不是和2个参数对应了(嘿嘿🙊)

1
2
3
4
5
6
7
dog = Dog('wangwang')  # 实例化dog

dog.eat()


# 运行结果:
吃肉!

终于正确了,那么传入的这个参数到底是哪个变量呢?

1
2
3
4
5
6
7
8
9
10
11
12
dog = Dog('wangwang')  # 实例化dog
print(dog.name)
print(dog.age)
print(dog.gender)

# 运行结果:
Traceback (most recent call last):
File "7.py", line 36, in <module>
print(dog.name)
AttributeError: 'Dog' object has no attribute 'name'
# 没有name变量
# 这时应该已经猜出传入的参数给gender变量了
1
2
3
4
5
6
7
8
dog = Dog('wangwang')  # 实例化dog
print(dog.gender)
dog.eat()


# 运行结果:
wangwang
吃肉!

可以看到,的确是传给了gender变量了,那么为什么只需要一个参数呢?超类的两个参数不翼而飞?其实如果你理解了上一点中的重构方法,就会发现上面的代码中根本就是重构了超类了__init__()方法,已经和超类中的__init__()无关了🤯

那如果超类的参数和子类的参数都需要呢?Python3中,使用super()方法将超类的构造函数复制到子类中,用法如下:

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
39
40
41
42
43
44
45
46
class Animal:
'''
所有动物的超类
'''

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

def eat(self):
print('动物都会吃饭')

def talk(self):
print('动物都会叫')


class Dog(Animal):
'''
继承Animal
'''

def __init__(self, name, age, gender):
super().__init__(name, age) # 使用super方法
self.gender = gender

def smell(self):
print('狗的嗅觉灵敏')

def eat(self):
'''
改写超类eat方法
'''
print('吃肉!')


dog = Dog('wangwang', 5, 'male') # 实例化dog
print(dog.name)
print(dog.age)
print(dog.gender)
dog.eat()

# 运行结果:
wangwang
5
male
吃肉!

从上面代码中可以看到super()其实是超类的一个对象,上述代码的思想是:给子类传入3个参数,前2个参数再传给超类,从而将超类需要的参数传入

多态性

什么是多态呢?先举个例子哇:今天班级大扫除,老师要分配工作,现有1~10共10个学生,老师肯定会这样说:“1,2,3,4,5去扫地,6,7,8,9,10拖地”,绝对不会这样说:“1去扫地,2去扫地,3去扫地,4去扫地,5去扫地,6去拖地,7去拖地,8去拖地,9去拖地,10去拖地”。这其实就是多态性,扫地是一个接口,多个人共用,拖地时一个接口,多个人共用。在Python中,多态的例子有很多,最常见的恐怕就是len()这个内置函数了,你会发现不论是字符串,还是列表,或者元组等都可以使用len()来统计长度,类似这种接口复用的方法就体现了多态性。

多态性概念:允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作

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
class Animal:
def __init__(self, name):
¦ self.name = name


class Dog(Animal):
def eat(self):
¦ print('{0} eat cat'.format(self.name))


class Cat(Animal):
def eat(self):
¦ print('{0} eat mouse'.format(self.name))


class Mouse(Animal):
def eat(self):
¦ print('{0} eat {0}'.format(self.name))


def eat(obj): # 一个接口,多种用法
obj.eat()


dog = Dog('狗')
cat = Cat('猫')
mouse = Mouse('老鼠')

eat(dog)
eat(cat)
eat(mouse)
1
2
3
4
# 运行结果:
狗 eat cat
猫 eat mouse
老鼠 eat 老鼠

通过以上代码应该可以很清晰的了解多态性

总结

Python面向对象的知识马上就要结束了,还剩下特殊方法、旧类和新类的不同之处,下次见~