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