#4 Python面向对象(三)
前两节讲解了Python面向对象的思想和Python类中各种变量的含义以及区别。肯定有小伙伴会问,类初始化时是否可以传入参数?如果有多个类中含有共同的函数方法,是否可以重复利用?本节就带着这些问题来继续深入类。Here We Go!
一、类的传参
带参数的初始化
还是以Doctor类为例,假如lisi是一位男性医生,现在要求在生成lisi这个对象时传入其性别。怎么办呢?
按照之前所学的,类似于函数的传参,肯定是:
1 | lisi = Doctor('male') |
的确,类带参数的初始化就是这么用的,那么再按照函数的思想,类源代码应该这么写:
1 | class Doctor(name): |
你已经看到,代码里已经标出这是错误的编写方法,不信的话来运行一下:
1 | Traceback (most recent call last): |
这是为什么呢?莫名其妙的竟然抛出未定义异常,关于这个问题将会放到下面(类的性质)来讲
那带参数的类要怎么编写呢?使用特殊方法 __init__(注意:init左右两边都是两个下划线)
,先来看个例子:
1 | class Doctor(): |
通过上面的代码可以看出,要想创建一个带参数的类,类里面要创建一个__init__的方法,其实这个特殊的函数叫做构造函数,构造函数会在实例化类后默默运行一次,也就是说,只要实例化了一个类,那么就已经运行了构造函数。构造函数通常用来传入参数使用。以上就是类的传参。
类实例化后运行顺序
当一个类实例化后,类中的代码被首先运行,其次是构造函数里的代码,再然后是被调用的函数被运行,最后是析构函数被运行(析构函数将放在类的特殊方法讲)
1 | class Doctor(): |
从上面的代码中可以清晰的看到每个代码块的运行顺序
二、面向对象的性质
面向对象共有三大性质:封装性、继承性、多态性。这三大性质是一定要记住的,不仅要记住,更要理解它们👹
封装性
先举个栗子🌰哇,一所学校有教学楼、图书馆、宿舍楼,你作为这个学校的学生,你当然可以随意使用这三个地点,但是外来人员恐怕就不能使用了,学校阻止外来人员的办法是建立围墙,将学校围起来。这就是封装的含义,建立围墙围学校(类)就是封装,只有学生(类的对象)可以使用学校资源,这两点加起来就是封装性。
封装性概念:把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。对内部数据进行了保护,防止程序其他部分使用或修改内部数据
总的来说,封装性就是安全+自私🤭
继承性
还记得前言中带的问题吗?当多个类拥有同样的方法时,是否可以只写一次重复利用,这就是继承的优势。
继承继承,顾名思义,就是继承🤥,父亲和儿子嘛
继承性:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
继承者称为:子类、派生类;被继承者称为:基类、父类、超类;在Python中,被继承者更习惯称为超类
说了这么多枯燥难以理解的概念,其实只需要一些例子就够了:
1 | class Animal: |
从上面的代码可以看到,原来 类(超类)
括号里面时超类啊,不是指参数。调用的例子: 1
2
3
4
5
6
7
8
9
10dog = Dog() # 实例化dog
cat = Cat() # 实例化cat
dog.eat() # dog调用了超类Animal的方法
dog.talk()
dog.smell() # dog调用自己的smell方法
cat.eat() # cat也是一样的
cat.talk()
cat.tree()
1 | # 运行结果: |
通过继承,可以大大增加代码的利用率。
人类社会中,继承往往是长江后浪推前浪,一浪更比一浪强!Python中的子类当然也可以将超类拍死在沙滩上🥴,那就是改写超类方法方便自己
继承性——改写不带参数的超类
1 | class Animal: |
1 | # 运行结果: |
继承性——改写带参数的超类
超类带参数,子类不带参数 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 | dog = Dog('wangwang',5) # 实例化dog,传入超类需要的参数 |
超类带参数,子类带参数 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
32class 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
10dog = 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 | dog = Dog('wangwang',5) # 实例化dog |
根据异常信息可知,init()需要2个参数,但是给了3个。
?????怎么又成3个了?????
难不成传入1个会变成2个?岂不是和2个参数对应了(嘿嘿🙊)
1 | dog = Dog('wangwang') # 实例化dog |
终于正确了,那么传入的这个参数到底是哪个变量呢?
1 | dog = Dog('wangwang') # 实例化dog |
1 | dog = Dog('wangwang') # 实例化dog |
可以看到,的确是传给了gender变量了,那么为什么只需要一个参数呢?超类的两个参数不翼而飞?其实如果你理解了上一点中的重构方法,就会发现上面的代码中根本就是重构了超类了__init__()方法,已经和超类中的__init__()无关了🤯
那如果超类的参数和子类的参数都需要呢?Python3中,使用super()方法将超类的构造函数复制到子类中,用法如下:
1 | class Animal: |
从上面代码中可以看到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 | class Animal: |
1 | # 运行结果: |
通过以上代码应该可以很清晰的了解多态性
总结
Python面向对象的知识马上就要结束了,还剩下特殊方法、旧类和新类的不同之处,下次见~