再看抽象類別


抽象類別 中曾經討論過定義抽象類別的幾個方式,其中提及,可以使用abc模組中的ABCMeta及abstractmethod來協助定義抽象類別:
import random
from abc import ABCMeta, abstractmethod

class GuessGame(metaclass=ABCMeta):
    @abstractmethod
    def message(self, msg):
        pass

    @abstractmethod
    def guess(self):
        pass    
    略...

其中ABCMeta就是個metaclass,而abstractmethod是個修飾器,在了解它們的實作之前,可以先了解,你可以定義類別的__abstractmethods__,指明某些特性是抽象方法。例如:
>>> class AbstractX:
...     def doSome(self):
...         pass
...     def doOther(self):
...         pass
...
>>> AbstractX.__abstractmethods__ = frozenset({'doSome', 'doOther'})
>>> x = AbstractX()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class AbstractX with abstract methods doOther, doSome
>>>


在類別建立之後,指定其__abstractmethods__特性,__abstractmethods__接受集合物件,集合物件中的字串表明哪些方法是抽象方法,如果一個類別的__abstractmethods__集合物件不為空,那它是個抽象類別,不可以直接實例化。

子類別不會看的到父類別的__abstractmethods__。例如:
>>> class ConcreteX(AbstractX):
...     pass
...
>>> x = ConcreteX()
>>> ConcreteX.__abstractmethods__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: __abstractmethods__
>>>

所以子類別若沒有實作父類別所有抽象方法,若亦想要定義抽象方法,則必須定義自己的__abstractmethods__。例如:
>>> class SubAbsX(AbstractX):
...     def doSome(self):
...         print('Some')
...
>>> SubAbsX.__abstractmethods__ = frozenset({'doOther'})
>>> x = SubAbsX()

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class SubAbsX with abstract methods doOther
>>>


了解以下之後,可以嘗試實作一個簡單的ABCMeta及abstractmethod:
def abstract(func):
func.__isabstract__ = True
return func

class Abstract(type):
def __new__(metaclz, definedclzname, supers, attrs):
clz = type.__new__(metaclz, definedclzname, supers, attrs)
# 這個類別自己定義的抽象方法
abstracts = {name
for name, value in attrs.items()
if getattr(value, "__isabstract__", False)}
# 從父類別中繼承下來的抽象方法
for super in supers:
for name in getattr(super, "__abstractmethods__", set()):
value = getattr(clz, name, None)
# 如果類別有定義自己的方法,則該方法就不會有 __isabstract__
# 就不會被加入 abstracts
if getattr(value, "__isabstract__", False):
abstracts.add(name)

# 這個類別總共的抽象方法
clz.__abstractmethods__ = frozenset(abstracts)

return clz

你可以這麼使用:
class AbstractX(metaclass=Abstract):
    @abstract
    def doSome(self):
        pass

# Can't instantiate abstract class AbstractX with abstract methods doSome
x = AbstractX()

子類別也必須定義所有的抽象方法:
class SubAbstractX(AbstractX):
    pass

# Can't instantiate abstract class SubAbstractX with abstract methods doSome
x = SubAbstractX()

只有在實作所有抽象方法時,才可以實例化:
class ConcreteX(AbstractX):
    def doSome(self):
        print('XD')
   
x = ConcreteX()
x.doSome()