B站清华128小时Python高级教程一(C3深入类和对象)
Chapter3 深入类和对象
一、多态和鸭子类型
1、多态:与继承(是指发生在子类和父类之间的)、重写(子类重写父类中的方法)相关。
class Animal: def say(self): print('I am an animal.') class Cat(Animal): def say(self): print('I am a cat.') class Dog(Animal): def say(self): print('I am a dog.') class Duck(Animal): def say(self): print('I am a duck.') duck = Duck() duck.say() dog = Dog() dog.say()
多态中的“重写父类方法”也是面向对象语言的一种通用方式。
2、所谓的鸭子方法听视频里讲基本就是对一个对象(变量)而言,不管它实际是什么,一会儿指向A,一会儿指向B,再指向C,只需要保证任何时候这个对象(变量)都具有同一种方法即可。
如下代码中变量animal先被赋值为Cat类,后被赋值为Duck类,但它都可以实现say()方法。
class Cat: def say(self): print('I am a cat.') class Dog: def say(self): print('I am a dog.') class Duck: def say(self): print('I am a duck.') animal = Cat animal().say() animal = Duck animal().say()
# 关于鸭子类型的实现案例 list_a = [1, 2] list_b = [3, 4] list_a.extend(list_b) #这里的可追加显而易见,因为list_a和list_b都同属列表类型 print(list_a) set_data = set() set_data.add(5) set_data.add(6) # 能否再继续在list_a上追加集合set_data呢 list_a.extend(set_data) print(list_a) #依然可以 # 那么为何可以呢?list_a是列表,而set_data是集合 # 原因就在于extend()方法实现上只需要是迭代类型就可以进行追加 # 这里list和set两种类型都实现了可迭代功能,这种情况也称之为“鸭子类型”
二、抽象基类
在原先如Java中是不支持多继承的,那么针对这种需求,JAVA会提供一个特殊的类,即抽象类,无法实例化,且可以被继承,且继承抽象流之后必须重载(重写)这个抽象类的方法。
Python中也有抽象类,其作用一般为:1、进行类型判断;2、对类的方法进行限制。
如Sized就是一个python中的抽象基类,同样有:1、无法被实例化;2、继承它的类定义时须重写其方法(对Sized类这里的方法就是__len__()方法)。
其中第1条无法被实例化的原因是否也是因为这个抽象基类并没有真正实现其内部方法呢?导致实例化对象方法缺陷报错?
梳理一下:
*为什么需要抽象基类?
- 某些类不能直接使用,只能被继承;
- 所有子类必须实现特定方法,避免遗漏;
- 能通过
isinstance()判断对象是否符合某种“行为协议”。
abc 实现。*抽象基类的核心作用
- 强制子类实现指定方法
如果子类没有重写所有抽象方法,尝试实例化时会抛出TypeError。 - 支持类型判断(协议式编程)
可用isinstance(obj, MyABC)判断对象是否符合某个抽象接口,即使它不是直接继承而来。
*抽象基类的定义
使用 abc 模块中的 ABC 类和 @abstractmethod 装饰器:
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def make_sound(self):
pass # 抽象方法,子类必须实现
class Dog(Animal):
def make_sound(self):
return "Woof!"
# 错误示例:未实现抽象方法
# class Cat(Animal):
# pass
# Cat() # 报错:Can't instantiate abstract class Cat with abstract method make_sound
对于上例中Cat类未实现抽象基类的方法而报错,这里的报错功能是由ABC类提供;而装饰器@abstractmethod是用来标识一个抽象基类中哪些方法是需要被重写的,ABC类需要先检查这个抽象类中有哪些必须重写的方法。(如已标识为必须重写的方法未重写,即报错)
三、关于isinstance()和type()方法的区别
class A:
pass
class B(A):
pass
b = B()
# 判断b与A,B是否是同一类型
# 用isinstance判断
print(isinstance(b, B)) #输出true
print(isinstance(b, A)) #输出true
# 用type判断
print(type(b) is B) #输出true
print(type(b) is A) #输出false
instance()和type()的区别在于,type()只会返回和被判别对象本身的“类别”,并不会返回其父类。而instance()中只要存在继承关系,都判别为true.
对任意对象,下例都满足。
print(isinstance(b, object))
四、类变量与实例变量(对象变量)的区别
class A:
# 类变量
b = 1
def __init__(self, x, y):
# 实例变量
self.x = x
self.y = y
a = A(2, 3)
# 实例对象可以访问实例变量和类变量
print(a.x, a.y, a.b)
# 类访问类变量
print(A.b)
# 使用类去修改类变量,并用实例对象访问类对象
A.b = 18
print(A.b)
print(a.b) # 先前创建的实例对象再去访问类变量,可以发现同样也改变了,说明这里是“引用类型”
# 使用实例对象去修改类变量
a.b = 26
print(A.b) # 可以发现类变量并没有变化
print(a.b) # 但是实例对象访问b却发生了改变
# 这里根本的原因是a.b = 26这一句的实质是给实例对象新创建了一个实例变量,这个实例变量只归属于这个实例对象
# 也就是说实例对象是无法修改类变量的
# 再使用类去修改类变量
A.b = 28
print(A.b) # 28
print(a.b) # 26,访问的还是a之前新创建的实例变量b,而非类变量b
五、类变量与实例变量以及查找顺序
class A:
name = 'A'
def __init__(self):
self.name = 'obj'
a = A()
# 访问name变量,会在a内部先查找实例对象,如果没有,再去类中找
print(a.name)
类的多继承查找一般有两种方式:深度优先和广度优先;不同的继承关系有着不同的调用顺序
# 深度优先:从左往右查找,对A来说,即先查找B,再找D,再找C,再找E,再找Object,再没有就抛出异常
class E:
pass
class D:
pass
class C(E):
pass
class B(D):
pass
class A(B, C):
pass
# 通过mro方法进行查找顺序的打印
print(A.__mro__)
# 输出:(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.C'>, <class '__main__.E'>, <class 'object'>)
# 广度优先
class D:
pass
class C(D):
pass
class B(D):
pass
class A(B, C):
pass
# 通过mro方法进行查找顺序的打印
print(A.__mro__)
浙公网安备 33010602011771号