一、python多态与鸭子类型
在python的面向对象:不同的对象在接收相同方法或者函数时会产生不同的行为,也就是说,每个对象可以用自己的方式去响应共同的函数,不同的方式实现不同的结果,即定义时的类型与运行时的类型不一样,则称为多态。
实现多态要有2个前提:1.继承:子类与父类之间;2.重写:父类的方法或函数
如果你还是有疑问,可以看一下下面的例子,加深理解,如下:
def __init__(self,name,area,type):
self.name=name
self.area=area
self.type=type
def go_home(self):
if self.type == '黄种人':
print('%s要回%s' %(self.name,self.area))
elif self.type == '黑种人':
print('%s要回%s' %(self.name,self.area))
elif self.type == '白种人':
print('%s要回%s' %(self.name,self.area))
elif self.type == '蓝种人':
print('%s要回%s' %(self.name,self.area))
else:
print('不知道家在哪里')
class Melanoderm(Person):
pass
class Yellow(Person):
pass
class White(Person):
pass
#定义不同的对象
M=Melanoderm('Rose','非洲','黑种人')
Y=Yellow('Alert','美国','白种人')
W=White('小明','中国','黄种人')
#不同对象实现相同的函数方法,达到不同的结果
def func(obj):
obj.go_home()
func(M)
func(Y)
func(W)
运行结果:
Rose要回非洲 Alert要回美国 小明要回中国
说到这里,大家是否对多态有疑问呢?为什么要使用多态呢?
1.增加了程序的灵活性,使用者都是同一种形式去调用,如func(obj)。
2.增加了程序可扩展性,通过继承Person类创建了一个新的类,使用者无需更改自己的代码,还是用func(obj)去调用 。
下面在原有基础的代码上新增1个实例化对象,看看调用方式有没有变化?
class Person:
def __init__(self,name,area,type):
self.name=name
self.area=area
self.type=type
def go_home(self):
if self.type == '黄种人':
print('%s要回%s' %(self.name,self.area))
elif self.type == '黑种人':
print('%s要回%s' %(self.name,self.area))
elif self.type == '白种人':
print('%s要回%s' %(self.name,self.area))
elif self.type == '蓝种人':
print('%s要回%s' %(self.name,self.area))
else:
print('不知道家在哪里')
class Melanoderm(Person):
pass
class Yellow(Person):
pass
class White(Person):
pass
class Blue(Person): #增加一种形态的人种
pass
#定义不同的对象
M=Melanoderm('Rose','非洲','黑种人')
Y=Yellow('Alert','美国','白种人')
W=White('小明','中国','黄种人')
#增加一类对象
B=Blue('Caorl','某海岛','蓝种人')
#不同对象实现相同的函数方法
def func(obj):
obj.go_home()
func(M)
func(Y)
func(W)
#使用者调用方式不变
func(B)
通过上面的代码可知,当增加一种形态时候,完全不影响外部用户的调用方式,对使用者是透明的,只需在内部修改类代码实现即可,以上就是python多态,你get到了吗?
而“鸭子类型”又是什么呢?
动态语言调用实例方法时不检查类型,只要方法存在,参数正确,就可以调用,这就是动态语言的“鸭子类型”。它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
下面看一个例子:
鸭子类型与多态:
class Cat(object):
def info(self):
print('i am cat')
class Dog(object):
def info(self):
print('i am dog')
class Duck(object):
def info(self):
print('i am duck')
animal_list = [Cat, Dog, Duck]
for animal in animal_list:
# 加上(),它才是一个类的实例化,即对象,才可以调用相对应的方法
animal().info()
注意: 鸭子类型 在运行之前 Cat,Dog都是在列表里面,当作变量 当运行时,加上()调用info() 才明确Cat是一个类
二、抽象基类
抽象基类(abstract base class,ABC):抽象基类就是类里定义了纯虚成员函数的类。纯虚函数只提供了接口,并没有具体实现;抽象基类不能被实例化(不能创建对象),通常是作为基类供子类继承,子类中重写虚函数,实现具体的接口。
简而言之,抽象基类就是定义各种方法而不做具体实现的类,任何继承自抽象基类的类必须实现这些方法,否则无法实例化。
2.1 抽象基类有哪些应用特点呢?
1.检查某个类中是否有某种方法
2.强调某个子类必须实现某些方法
2.1.1 检查某个类中是否有某种方法
- 定义Demo类,类中含有__len__魔法方法
- 导入抽象基类中的Sized类
"""
abc模块 abc.py 不要以abc为模块名称
"""
class Demo(object):
def __init__(self, li):
self.li = li
def __len__(self):
return len(self.li)
l = ['c', 'chen', 'wu']
d = Demo(l)
# print(d) # 打印demo对象的地址 <__main__.Demo object at 0x0000025821DBA1D0>
# print(len(d)) # 会触发__len__ return len(self.li) 所以 返回3
"""
如何判断 Demo中 是否 含有 __len__魔法方法
"""
# print(hasattr(d,'__len__')) # 判断 d的内部 是否含有__len__ 返回值为True 可查看源码
"""
可以通过判断 d 是否是 Sized的子类 然后进一步判断 d这个对象的类 是否 含有 __len__方法
"""
from collections.abc import Sized
# 判断d 是否是 Sized 这个类
print(isinstance(d, Sized)) # True 说明d 是Sized 类型
【拓展】:查看Sized源码
class Sized(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __len__(self):
return 0
@classmethod
def __subclasshook__(cls, C):
if cls is Sized:
return _check_methods(C, "__len__")
return NotImplemented
Sized类里面封装了一个__len__ 魔法方法,此方法是一个抽象类的方法
2.1.2 强制子类必须实现父类的方法
下面举一个例子来理解此特点:
"""
强制子类重写父类
方法1 主动抛出异常
"""
class CacheBase(object):
def dele(self):
raise NotImplementedError
def creat(self):
raise NotImplementedError
class RedisBase(CacheBase):
"""
1.子类 如果不重写 父类 方法 访问时 直接抛出异常
"""
# 重写父类方法
def creat(self):
print('jsjcd')
r = RedisBase()
r.creat()
r.dele() # 不重写父类方法,报错
【注意】: 这个是正常的父类重写调用,继承自object基类, r.dele() 的方法调用时没有重写父类的方法,所以它会直接报错
下面再对比一下抽象基类是如何实现的?
"""
强制子类重写
方法2 抽象基类实现
"""
import abc
# 注意:不是直接继承object 而是继承abc.ABCMeta
class CacheBase(metaclass=abc.ABCMeta):
@abc.abstractmethod
def dele(self):
print('dele')
def creat(self):
print('creat')
class RedisBase(CacheBase):
def dele(self):
print('adgj')
def creat(self):
print('asdd')
r= RedisBase()
r.creat()
# r.dele()
【注意】:在父类定义方法时,需加上@abc.abstractmethod,否则在子类调用时,会出现异常。
三、type与isinstance区别
两者的区别主要有2点:
- type 不考虑 继承关系
- isinstance 考虑继承关系
下面先举一个简单例子说明:
"""
isinstance 与 type 基础使用
"""
a = 1
b = "耗子"
print(isinstance(b,(int, str))) # 判断b是否属于哪种类型
print(type(b)) # 直接验证b的数据类型
接着看以下例子,理解两者对于继承关系之间的区别:
"""
考虑继承
"""
class Father(object):
pass
class Son(Father):
pass
ls = Son()
# print(isinstance(ls,Son)) # True
# print(isinstance(ls, Father)) # True 继承关系
print(type(ls) is Son) # True
print(type(ls) is Father) # False 并不考虑继承关系 主要 两个类
【总结】: 通过以上的例子可以发现,isinstance是考虑继承关系的,而type不考虑继承关系,默认是两个类。
四、类属性与实例属性的关系
4.1 两者有何区别呢?
由于Python是动态语言,根据类创建的实例可以任意绑定属性, 给实例绑定属性的方法是通过实例变量,或者通过self变量:
class Student(object):
def __init__(self, name):
# 实例绑定属性
self.name = name
s = Student('Bob')
s.score = 90
但是,如果Student类本身需要绑定一个属性呢?可以直接在class中定义属性,这种属性是类属性,归Student类所有:
class Student(object):
name = 'Student'
当我们定义了一个类属性后,这个属性虽然归类所有,但类的所有实例都可以访问到吗?
>>> class Student(object):
... name = 'Student'
...
>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
>>> print(Student.name) # 打印类的name属性
Student
>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student
通过以上的例子,可以发现类的属性,实例也是可以访问到的,但如果实例中有属性,则优先取实例属性。
这里拓展一下两者的查找顺序:
1. 对象是可以向上查找的,所以可以访问到类属性
• 当对象自己有该实例属性时 ,则输出的是自己的
2. 类不能向下查找,所以只能访问到类属性
【总结】:
- 实例属性属于各个实例所有,互不干扰;
- 类属性属于类所有,所有实例共享一个属性;
- 不要对实例属性和类属性使用相同的名字,否则将产生难以发现的错误。
4.2 多继承的查找顺序是咋样的呢?
例如以下的多继承关系,它的查找顺序如何呢?
这里会涉及到一个经典算法:MRO算法,DFS(deep first search) 深度优先,它的查找顺序会如下:先左再右
但如果是菱形继承,查找顺序又是如何呢?
这里引入BFS(广度优先),它的查找顺序如下:
举一个简单的例子,如下:
棱形继承
class D(object):
pass
class B(D):
pass
class C(D):
pass
class A(B, C):
pass
# 查找顺序(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>)
# 通过className.__mro__来查看顺序 MRO算法
print(A.__mro__)
五、Python对象自省机制
Python中比较常见的自省(introspection)机制(函数用法)有: dir(),type(), hasattr(), isinstance(),通过这些函数,能够在程序运行时得知对象的类型,判断对象是否存在某个属性,访问对象的属性。
# python自省机制
class Person(object):
name ='chem'
class Student(Person):
def __init__(self,school_name):
self.school_name = school_name
chen = Student('华软')
print(chen.__dict__) # 当前对象的属性 {"属性":"属性的值"}
print(dir(chen)) # [] 查看属性和方法
以上举了一个简单例子,如想了解其他的自省机制,可参考此链接:www.cnblogs.com/ArsenalfanI…
六、super函数
下面先来了解一下super函数的简单使用:
super基础使用
"""
class A(object):
def __init__(self):
print('a')
class B(A):
def __init__(self):
print('b')
super().__init__()
b = B()
print(b)
以上是单继承关系,此运行会同时输出b、a。
但如果是多继承关系,它又会如何呢?
class A(object):
def __init__(self):
print("A")
class C(A):
def __init__(self):
print("B")
super().__init__()
class B(A):
def __init__(self):
print("C")
super().__init__()
class D(B,C):
def __init__(self):
print("D")
super().__init__()
if __name__ == '__main__':
d = D()
可以通过打印 print(D.mro)了解它的继承顺序,如下:
(<class 'main.D'>, <class 'main.B'>, <class 'main.C'>, <class 'main.A'>, <class 'object'>)