最近在通过源代码的方式学习python的抽象类。有一些心得体会,希望能记录下来。学习的时候是基于python3.6,以及cpython master上的源代码来了解的。我对c语言,以及cpython的实现并不熟悉,所以c实现部分只是大概了解了一下。
实现自己的抽象类
python的abc模块中定义了抽象类的metaclass类ABCMeta,以及抽象方法装饰器abstractmethod, abstractclassmethod, abstractstaticmethod,抽象property装饰器abstractproperty等。我们可以基于这些工具来实现自己的抽象类,比如
from abc import ABCMeta
from abc import abstractmethod
class MyAbstractClass(metaclass=ABCMeta):
@abstractmethod
def my_method(self):
raise NotImplementedError('my_method is not implemented')
不能实例化抽象类
首先尝试实例化MyAbstractClass看看:
inst = MyAbstractClass()
inst.my_method()
输出错误:
TypeError: Can't instantiate abstract class MyAbstractClass with abstract methods my_method
从错误报告可以看出,抽象类不允许实例化。我们知道实例化都是在__new__中进行了,查看ABCMeta无法找到这样的字符串。如果搜索cpython的实现,可以在“typeobject.c”中看到“object_new”方法会抛出这样的异常:
static PyObject *
object_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
...
if (type->tp_flags & Py_TPFLAGS_IS_ABSTRACT) {
...
PyErr_Format(PyExc_TypeError,
"Can't instantiate abstract class %s "
"with abstract methods %U",
type->tp_name,
joined);
...
}
return type->tp_alloc(type, 0);
}
从中,我们可以看到tp_flags如果有表示抽象的标志"Py_TPFLAGS_IS_ABSTRACT",则会抛出这样的异常。那“tp_flags”是怎么设置上这个标志的呢,同样在typeobject.c中:
static int
type_set_abstractmethods(PyTypeObject *type, PyObject *value, void *context)
{
/* __abstractmethods__ should only be set once on a type, inabc.ABCMeta.__new__, so this function doesn't do anythingspecial to update subclasses.*/
...
if (res == 0) {
PyType_Modified(type);
if (abstract)
type->tp_flags |= Py_TPFLAGS_IS_ABSTRACT;
else
type->tp_flags &= ~Py_TPFLAGS_IS_ABSTRACT;
}
return res;
}
从这里可以猜测,当一个类有通过抽象方法相关的装饰器装饰后的方法时,python在初始化这个类的时候,会设置这个类为抽象类,而不允许实例化。可以把“my_method”上的装饰器移除掉:
from abc import ABCMeta
from abc import abstractmethod
class MyAbstractClass(metaclass=ABCMeta):
def my_method(self):
raise NotImplementedError('my_method is not implemented')
inst = MyAbstractClass()
inst.my_method()
输出:
NotImplementedError: my_method is not implemented
可以看到,MyAbstractClass能够被初始化了。
标记抽象方法
刚才看到,C实现通过判断“__abstractmethods__ ”,以此来设置类是否是抽象类。在此可以先看下装饰器做了什么事情:
def abstractmethod(funcobj):
funcobj.__isabstractmethod__ = True
return funcobj
装饰器对函数对象增加了属性“__isabstractmethod__”。在ABCMeta的__new__中:
def __new__(mcls, name, bases, namespace):
cls = super().__new__(mcls, name, bases, namespace)
# Compute set of abstract method names
abstracts = {name
for name, value in namespace.items()
if getattr(value, "__isabstractmethod__", False)}
for base in bases:
for name in getattr(base, "__abstractmethods__", set()):
value = getattr(cls, name, None)
if getattr(value, "__isabstractmethod__", False):
abstracts.add(name)
cls.__abstractmethods__ = frozenset(abstracts)
...
return cls
可以看到在创建实例时,会获取类以及其父类中有"__isabstractmethod__",并且值为True的方法,保存在“__abstractmethods__”中。而python的C实现,正是通过判断“__abstractmethods__”,来决定是否是抽象类。
class MyAbstractClass(metaclass=ABCMeta):
@abstractmethod
def my_method(self):
raise NotImplementedError('my_method is not implemented')
print(MyAbstractClass.__abstractmethods__)
print(MyAbstractClass.my_method.__isabstractmethod__)
输出:
frozenset({'my_method'})
True
ABCMetada的__new__中有一个小细节, “value = getattr(cls, name, None)”,获取继承的父类的方法时,并不是直接从父类的“__abstractmethods__”中得到,而是通过getattr从子类本身获取,这个是处理当子类实现了父类的抽象方法时,获取的是子类的实现。
继承抽象类
首先写一个子类,但是不实现父类的抽象方法
class MyImplement(MyAbstractClass):
pass
print(MyImplement.__abstractmethods__)
print(MyImplement.my_method.__isabstractmethod__)
inst = MyImplement()
输出:
frozenset({'my_method'})
True
Traceback (most recent call last):
File "e:\test1.py", line 56, in
inst = MyImplement()
TypeError: Can't instantiate abstract class MyImplement with abstract methods my_method
可见由于父类的抽象方法未被覆盖,所以子类依然被认为是抽象类。如果实现了抽象方法呢
class MyImplement(MyAbstractClass):
def my_method(self):
print('my_method')
print(MyImplement.__abstractmethods__)
# 由于my_method方法被覆盖,没有__isabstractmethod__属性,此行会报错
# print(MyImplement.my_method.__isabstractmethod__)
inst = MyImplement()
inst.my_method()
输出:
frozenset()
my_method
由于子类实现了抽象方法(覆盖)。方法本身没有“__isabstractmethod__”,因此“__abstractmethods__”为空,子类可以初始化。
结论一个类是否是一个抽象类,是由类的__abstractmethods__来决定的。python的C实现会通过这个属性,来决定是否为抽象类。
__abstractmethods__是一个set,包含类中所有的抽象方法(包括继承的)。
通过检查类的方法(包括继承过来的方法),是否有“__isabstractmethod__”属性,且值为True,来判断方法是否是抽象方法。也即是说,如果类本身实现了父类的抽象方法,则此方法不再有“__isabstractmethod__”属性。
抽象方法装饰器做的,就是给方法加上“__isabstractmethod__”属性,并设置为True