抽象基類: 繼承的約束與協議
__doc__ = """
抽象基類: 繼承的約束與協議
# 抽象基類 --- 有點java的味道,也有點golang的味道,繼承,協議,接口
1.抽象基類不能實例化
2.必要時可以要求子類實現基類指定的抽象方法
# 抽象基類的目的:
1.處理繼承問題方面更加規範、系統
2.明確調用之間的相互關係
3.使得繼承層次更加清晰
4.限定子類實現的方法
# 參考
https://www.osgeo.cn/cpython/library/abc.html # 介紹
https://www.python.org/dev/peps/pep-3119/ # 緣由
https://www.cnblogs.com/traditional/p/11731676.html # 使用
"""
import abc
class Base(abc.ABC):
@abc.abstractmethod # 要求子類實現指定協議,抽象方法用@abstractmethod裝飾器標記,而且定義體中通常只有文檔字符串.
def my_protocol(self):
"""要求子類實現的自定義協議"""
def not_protocol(self):
"""不要求子類實現的自定義協議"""
@classmethod
def __subclasshook__(cls, subclass): # 同時作用於isinstance和issubclass
"""
此方法應返回 True , False 或 NotImplemented .
如果它回來 True , the 子類 被認爲是ABC的一個子類。
如果它回來 False , the 子類 不被認爲是ABC的一個子類,即使它通常是ABC的一個子類。
如果它回來 NotImplemented ,子類檢查繼續使用常規機制。
"""
# print("subclass.__mro__:", subclass.__mro__) # 繼承樹
if cls is Base:
if any("my_protocol" in B.__dict__ for B in subclass.__mro__):
return True
else:
return False
return NotImplemented
# 顯式繼承Base
class MyClass(Base):
"""子類"""
def my_protocol(self):
pass
# 顯式繼承Base
class MyClass2(Base):
"""子類"""
def my_protocol(self):
pass
@Base.register
class MyClass3():
"""虛擬子類:issubclass和isinstance等函數都能識別,但是註冊的類不會從抽象基類中繼承任何方法或屬性."""
k = MyClass()
print(isinstance(k, Base)) # True
print(issubclass(MyClass, Base)) # True
k2 = MyClass2()
print(isinstance(k2, Base)) # True
print(issubclass(MyClass2, Base)) # True
k3 = MyClass3()
print(isinstance(k3, Base)) # False
print(issubclass(MyClass3, Base)) # False
元類: 用來攔截和修改 繼承此元類 的子類的 創建
__doc__ = """
元類: 用來攔截和修改 繼承此元類 的子類的 創建
# 爲一個類定義確定適當的元類是根據以下規則:
1.如果沒有基類且沒有顯式指定元類,則使用 type();
2.如果給出一個顯式元類而且 不是 type() 的實例,則其會被直接用作元類;
3.如果給出一個 type() 的實例作爲顯式元類,或是定義了基類,則使用最近派生的元類。
# 目的
瞭解元類的目的前需要先知道普通繼承的作用與特點:
繼承的特點:
<1>減少代碼量和靈活指定型類
<2>子類具有父類的方法和屬性
<3>子類不能繼承父類的私有方法或屬性
<4>子類可以添加新的方法
<5>子類可以修改父類的方法
可以看到所有涉及到變動的特點都需要子類中實現,這樣的話,對於某些固定模式的變動就需要子類中重複實現,增加子類中的代碼量,
或者所有子類固定繼承某一箇中間類,並在中間類的__new__中根據子類模式去創建,元類就可以理解爲python中單獨提出來的中間類,
而一切類的創建最終都會調用type.__new__(cls, classname, bases, attrs)
元類的使用就是在子類創建時攔截並修改,可以依據子類的特點增加或修改屬性.
# 參考:
https://docs.python.org/zh-cn/3/reference/datamodel.html#object.__new__
https://docs.python.org/zh-cn/3/reference/datamodel.html#metaclasses
"""
import abc
from six import add_metaclass, with_metaclass # py2和py3的橋樑,元類相關的變化很大,可通過six做兼容.
Meta = abc.ABCMeta
# 通用做法。
@add_metaclass(Meta)
class MyClass(object):
pass
# 在Python 3 等價於
class MyClass(object, metaclass=Meta):
pass
# 在Python 2.x (x >= 6)中等價於
class MyClass(object):
__metaclass__ = Meta
pass
# 或者直接調用裝飾器,這裏也能看出來裝飾器就是個方法包裝而已。
class MyClass(object):
pass
MyClass = add_metaclass(Meta)(MyClass)
# 再或者用 with_metaclass
class MyClass(object, with_metaclass(Meta)):
pass
def with_metaclass(meta, *bases): # 代碼摘自sqlalchemy中對元類的包裝,採用了類似six.with_metaclass的方式
"""Create a base class with a metaclass.
Drops the middle class upon creation.
Source: http://lucumr.pocoo.org/2013/5/21/porting-to-python-3-redux/
"""
class metaclass(meta):
__call__ = type.__call__
__init__ = type.__init__
def __new__(cls, name, this_bases, d):
if this_bases is None:
return type.__new__(cls, name, (), d)
return meta(name, bases, d)
return metaclass("temporary_class", None, {})
# 舉例部分
# 繼承type 創建元類
class SayMetaClass(type):
# 用type動態生成類的的三個重要參數:類名稱、父類、屬性
def __new__(cls, name, bases, attrs):
# 創造"天賦"
attrs['say_' + name] = lambda self, value, saying=name: print(saying + ',' + value + '!')
# 類名稱、父類、屬性 生成元類
return type.__new__(cls, name, bases, attrs)
# 繼承元類 創建類
# class Hello(object, metaclass=SayMetaClass):
class Hello(object, with_metaclass(SayMetaClass)):
pass
# 創建實列
hello = Hello()
# 調用實例方法
hello.say_Hello('world!')
# 普通類的創建與繼承
class Man:
def __new__(cls, name, age): # 靜態方法
print('Man.__new__ called.', getattr(cls, 'work_copy')) # 能傳過來
return super(Man, cls).__new__(cls) # -->self
# 對比普通類的繼承與創建
class Person(Man):
"""普通繼承
對象是由 __new__() 和 __init__() 協作構造完成的 (由 __new__() 創建,並由 __init__() 定製),
所以 __init__() 返回的值只能是 None,否則會在運行時引發 TypeError。
"""
def __new__(cls, name, age, work): # 可以不寫,默認繼承了Man.__new__
print('Person.__new__ called.')
cls.work_copy = work
# return super(Person, cls).__new__(cls, name, age) # -->Man.__new__
cls = super(Person, cls).__new__(cls, name, age) # 根據需要修改新創建的實例再將其返回
cls.work = work # 可以看到,__new__也是可以做__init__的數值綁定的
return cls
def __init__(self, name, age, work): # 參數與__new__必須一致,__new__的後續構造
print('Person.__init__ called.')
self.name = name
self.age = age
# self.work = work
def __str__(self):
return '<Person: %s %s %s>' % (self.name, self.age, self.work)
zhangsan = Person('zhangsan', 24, '張三')
print(zhangsan)