Python抽象類以及元類

抽象基類: 繼承的約束與協議

__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)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章