python提升四
Python继承机制及其使用
继承机制经常用于创建和现有类功能类似的新类,又或是新类只需要在现有类基础上添加一些成员(属性和方法),但又不想直接将现有类代码复制给新类。也就是说,通过使用继承这种机制,可以轻松实现类的重复使用。
举个例子,假设现有一个 Shape 类,该类的 draw() 方法可以在屏幕上画出指定的形状,现在需要创建一个 Form 类,要求此类不但可以在屏幕上画出指定的形状,还可以计算出所画形状的面积。要创建这样的类,笨方法是将 draw() 方法直接复制到新类中,并添加计算面积的方法。
就是使用类的继承机制。实现方法为:让 From 类继承 Shape 类,这样当 From 类对象调用 draw() 方法时,Python 解释器会先去 From 中找以 draw 为名的方法,如果找不到,它还会自动去 Shape 类中找。如此,我们只需在 From 类中添加计算面积的方法即可,示例代码如下
1 | class Shape: |
上面代码中,class From(Shape) 就表示 From 继承 Shape。
Python 中,实现继承的类称为子类,被继承的类称为父类(也可称为基类、超类)。因此在上面这个样例中,From 是子类,Shape 是父类。
子类继承父类时,只需在定义子类时,将父类(可以是多个)放在子类之后的圆括号里即可
语法格式:
class 类名(父类1, 父类2, …):
类定义部分注意,如果该类没有显式指定继承自哪个类,则默认继承 object 类(object 类是 Python 中所有类的父类,即要么是直接父类,要么是间接父类)。另外,Python 的继承是多继承机制(和 C++ 一样),即
一个子类可以同时拥有多个直接父类。
1 | class People: |
没错,子类拥有父类所有的属性和方法,即便该属性或方法是私有(private)的(与c++的区别)
关于Python的多继承
使用多继承经常需要面临的问题是,多个父类中包含同名的类方法。对于这种情况,Python 的处置措施是:
根据子类继承多个父类时这些父类的前后次序决定,即排在前面父类中的类方法会覆盖排在后面父类中的同名类方法。
1 | class People: |
一般不使用多继承 因为多继承容易使代码变得复杂化
Python父类方法重写
子类继承了父类,那么子类就拥有了父类所有的类属性和类方法。通常情况下,子类会在此基础上,扩展一些新的类属性和类方法。
但凡事都有例外,我们可能会遇到这样一种情况,即子类从父类继承得来的类方法中,大部分是适合子类使用的,但有个别的类方法,并不能直接照搬父类的,如果不对这部分类方法进行修改,子类对象无法使用。针对这种情况,我们就需要在子类中重复父类的方法。
举个例子,鸟通常是有翅膀的,也会飞,因此我们可以像如下这样定义个和鸟相关的类:
1 | class Bird: |
但是,对于鸵鸟来说,它虽然也属于鸟类,也有翅膀,但是它只会奔跑,并不会飞。针对这种情况,可以这样定义鸵鸟类:
1 | class Ostrich(Bird): |
可以看到,因为 Ostrich 继承自 Bird,因此 Ostrich 类拥有 Bird 类的 isWing() 和 fly() 方法。其中,isWing() 方法同样适合 Ostrich,但 fly() 明显不适合,因此我们在 Ostrich 类中对 fly() 方法进行重写。
重写,有时又称覆盖,是一个意思,指的是对类中已有方法的内部实现进行修改。
如何调用被重写的方法
事实上,如果我们在子类中重写了从父类继承来的类方法,那么当在类的外部通过子类对象调用该方法时,Python 总是会执行子类中重写的方法。
如果想调用父类中被重写的这个方法
很简单,前面讲过,Python 中的类可以看做是一个独立空间,而类方法其实就是出于该空间中的一个函数。而如果想要全局空间中,调用类空间中的函数,只需要在调用该函数是备注类名即可。举个例子:
1 | class Bird: |
此程序中,需要大家注意的一点是,使用类名调用其类方法,Python 不会为该方法的第一个 self 参数自定绑定值,因此采用这种调用方法,需要手动为 self 参数赋值。
Python super()函数:调用父类的构造方法
Python 中子类会继承父类所有的类属性和类方法。严格来说,类的构造方法其实就是实例方法,因此毫无疑问,父类的构造方法,子类同样会继承。
Python 是一门支持多继承的面向对象编程语言,如果子类继承的多个父类中包含同名的类实例方法,则子类对象在调用该方法时,会优先选择排在最前面的父类中的实例方法
显然,构造方法也是如此。
1 | class People: |
Person 类同时继承 People 和 Animal,其中 People 在前。这意味着,在创建 per 对象时,其将会调用从 People 继承来的构造函数。因此我们看到,上面程序在创建 per 对象的同时,还要给 name 属性进行赋值。
但如果去掉最后一行的注释,运行此行代码,Python 解释器会报错
这是因为,从 Animal 类中继承的 display() 方法中,需要用到 food 属性的值,但由于 People 类的构造方法“遮蔽”了Animal 类的构造方法,使得在创建 per 对象时,Animal 类的构造方法未得到执行,所以程序出错。
针对这种情况,正确的做法是定义 Person 类自己的构造方法(等同于重写第一个直接父类的构造方法)。但需要注意,如果在子类中定义构造方法,则必须在该方法中调用父类的构造方法。
在子类中的构造方法中,调用父类构造方法的方式有 2 种,分别是:
1.
类可以看做一个独立空间,在类的外部调用其中的实例方法,可以向调用普通函数那样,只不过需要额外备注类名(此方式又称为未绑定方法);
2.
使用 super() 函数。但如果涉及多继承,该函数只能调用第一个直接父类的构造方法。
super() 语法格式
super().__init__(self,...)
修改上面的程序:
1 | class People: |
可以看到,Person 类自定义的构造方法中,调用 People 类构造方法,可以使用 super() 函数,也可以使用未绑定方法。但是调用 Animal 类的构造方法,只能使用未绑定方法。
Python __slots__:限制类实例动态添加属性和方法
Python 也允许动态地为类或实例对象添加方法
我们知道,类方法又可细分为实例方法、静态方法和类方法,Python 语言允许为类动态地添加这 3 种方法;
但对于实例对象,则只允许动态地添加实例方法,不能添加类方法和静态方法。这个在前面已经提到过
为单个实例对象添加方法,不会影响该类的其它实例对象;而如果为类动态地添加方法,则所有的实例对象都可以使用
1 | class CLanguage: |
Python 提供了 slots 属性,通过它可以避免用户频繁的给实例对象动态地添加属性或方法。
再次声明,slots 只能限制为实例对象动态添加属性和方法,而无法限制动态地为类添加属性和方法。
slots 属性值其实就是一个元组,只有其中指定的元素,才可以作为动态添加的属性或者方法的名称
1 | class CLanguage: |
可以看到, CLanguage 类中指定了 slots 属性,这意味着,该类的实例对象仅限于动态添加 name、add、info 这 3 个属性以及 name()、add() 和 info() 这 3 个方法。
注意,对于动态添加的方法,slots 限制的是其方法名,并不限制参数的个数。
Python type()函数:动态创建类
type() 函数属于 Python 内置函数,通常用来查看某个变量的具体类型。
其实,type() 函数还有一个更高级的用法,即创建一个自定义类型(也就是创建一个类)。
type() 函数的语法格式有 2 种:
type(obj)
type(name, bases, dict)
这 2 种语法格式,各参数的含义及功能分别是:
第一种语法格式用来查看某个变量(类对象)的具体类型,obj 表示某个变量或者类对象。
第二种语法格式用来创建类,
其中 name 表示类的名称;
bases 表示一个元组,其中存储的是该类的父类;
dict 表示一个字典,用于表示类内定义的属性或者方法。
1 | def say(self): |
注意,Python 元组语法规定,当 (object,) 元组中只有一个元素时,最后的逗号(,)不能省略。
如何判断 dict 字典中添加的是方法还是属性?
很简单,如果该键值对中,值为普通变量(如 “xiaoyang”),则表示为类添加了一个类属性;反之,如果值为外部定义的函数(如 say() ),则表示为类添加了一个实例方法。
可以看到,使用 type() 函数创建的类,和直接使用 class 定义的类并无差别。
事实上,我们在使用 class 定义类时,Python 解释器底层依然是用 type() 来创建这个类。