Python中的抽象基类
1.说在前头
"抽象基类"这个词可能听着比较"深奥",其实"基类"就是"父类","抽象"就是"假"的意思,
"抽象基类"就是"假父类."
2.对之前元类的一点补充
之前说过通过元类实例化类的语法是
变量名 = type("类名", ("继承的类",), {"属性名":"属性值"})
现在介绍另一种方法
class 类名(metaclass=元类名):
...
举个例子:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# __author__: kainhuck
# 定义一个元类
class MyMetaClass(type):
def __new__(cls, name, bases, attrs):
'''
:param name: 类名
:param bases: 继承的基类
:param attrs: 拥有的属性
要求必须含有name属性
'''
name = attrs.get("name", None)
if name and not callable(name):
return type.__new__(cls, name, bases, attrs)
raise NotImplementedError("必须含有name属性")
# 定义普通类
# class MyClassA(metaclass=MyMetaClass): # 报错,没有定义name属性
# pass
# 定义普通类
# class MyClassB(metaclass=MyMetaClass): # 报错,没有定义name属性
# def name(self):
# pass
#
# class MyClassC(metaclass=MyMetaClass): # 报错,没有定义name属性
# def __init__(self):
# self.name = "kainhuck"
#
class MyClassD(metaclass=MyMetaClass): # 没有报错
name = "kainhuck"
3.鸭子类型
鸭子类型:如果一个东西看起来想一个鸭子,叫起来像一个鸭子,那么它大概就是一只鸭子.
在Python中有些时候我们需要一个有某个功能(比如说:鸭子叫)的对象,那我们可以通过判断这个对象是不是一只鸭子来检测是否满足我们的需求;但仔细想想这有些缺陷,因为我们真正需要的是鸭子叫
这个方法,一个对象无论是不是鸭子只要他会像鸭子一样叫就可以啦.
这话可能有些绕,让我来举一个例子
有这么一个函数:
def func(something):
print(something[0])
这个函数会打印出参数的第一个元素,其实这里隐含着一个条件--参数支持下标索引.为了使代码完善我们应该对该函数做点修改.
方案一.
def func(something):
if isinstance(something, (list, tuple, set)): # 这些方法支持下标索引
print(something[0])
else:
print("Error")
方案一的缺点:
这样写就默认把something的类型限定了,拓展性很差
我们知道只要自定义的类实现了
__getitem__
方法就可以使其支持下标索引.
方案二.
def func(something):
if hasattr(something, '__getitem__'):
print(something[0])
else:
print("Error")
方案二的缺点:
并不是所有实现
__getitem__
的方法都可以支持下标索引,比如字典
类型
这样似乎没有解决方案了..其实抽象基类就完美的解决了这问题
4.抽象基类
1. 抽象基类的定义:
由abc.ABCMeta这个元类实现的类就是抽象基类,如
class AbstractClass(metaclass=abc.ABCMeta):
pass
2. register方法
定义好的抽象基类通过register
方法可以成为别的类的父类
举个例子:
import abc
# 定义一个抽象基类
class AbstractClass(metaclass=abc.ABCMeta):
pass
# 定义一个普通类继承自object
class MyClass(object):
pass
# 把我们定义的抽象基类注册为MyClass的父类
AbstractClass.register(MyClass)
mc = MyClass()
print(issubclass(MyClass, AbstractClass)) # 输出True
print(isinstance(mc, AbstractClass)) # 输出True
# 将我们定义的抽象基类注册到系统定义的类
AbstractClass.register(list)
print(isinstance([], AbstractClass)) # 输出True
说明一点:抽象基类虽然可以成为别的类的父类,但是别的类并不会继承抽象基类的方法和属性
3. 对前面例子的方案三实现
方案三.
from abc import ABCMeta
class MySequence(metaclass=ABCMeta):
pass
MySequence.register(list) # 注册为列表的父类
MySequence.register(tuple) # 注册为元组的父类
'''
也可以自定义一个类,将MySequence注册为其父类
'''
def func(something):
if isinstance(something, AbstractClass): # AbstractClass的子类
print(something[0])
else:
print("Error")
4.__subclasshook__
魔法方法
看过上面的例子你们肯定会觉得,给每个类都注册一遍抽象基类太麻烦了,没错Python的开发者也这么觉得,于是__subclasshook__
这个方法出现了
几点说明:
- 该方法定义在抽象基类中
- 该方法必须定义为类方法
- 该方法有三个返回值
- True: 如果测试类被认为是子类
- False: 如果测试类不被认为是子类
- NotImplemented: 这个后面讲
定义一个抽象基类:
import abc
class AbstractDuck(metaclass=abc.ABCMeta):
@classmethod
def __subclasshook__(cls, subclass):
quack = getattr(subclass, 'quack', None) # 取出subclass的 quack 属性,如果不存在则返回 None
return callable(quack) # 返回quack是否可以调用(是否是个方法)
定义两个测试类
class Duck(object):
def quack(self):
pass
class NotDuck(object):
quack = "foo"
判断是否是AbstractDuck的子类
print(issubclass(Duck, AbstractDuck)) # 输出 True
print(issubclass(NotDuck, AbstractDuck)) # 输出 False
注意:__subclasshook__
方法的优先级大于register
举个例子解释NotImplemented
返回值
In [6]: import abc
In [7]: class AbstractDuck(metaclass=abc.ABCMeta):
...: @classmethod
...: def __subclasshook__(cls, subclass):
...: quack = getattr(subclass, 'quack', None) # 取出subclass的 quack 属性,如果不存在则返回 None
...: if callable(quack):
...: return True
...: return NotImplemented
...:
In [8]: class Duck(object):
...: def quack(self):
...: pass
...:
In [9]: class NotDuck(object):
...: quack = "foo"
...:
In [10]: issubclass(NotDuck, AbstractDuck)
Out[10]: False
In [11]: AbstractDuck.register(NotDuck)
Out[11]: __main__.NotDuck
In [12]: issubclass(NotDuck, AbstractDuck)
Out[12]: True