python的抽象类详解_Python的抽象类学习-1

最近在通过源代码的方式学习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

你可能感兴趣的:(python的抽象类详解)