前言

前两节讲解了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中类的不同之处,下次见~

03-03 00:13