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