1. 程式人生 > >面向對象:元類、

面向對象:元類、

mysq print {} meta pos 代碼 let new 說明

元類:

python中一切皆對象,意味著:

  1. 都可以被引用,如 x = obj

  2. 都可以被當做函數的參數傳入

  3. 都可以被當做函數的返回值

  4. 都可以當做容器類的元素(列表、字典、元祖、集合),如: l = [func,time,obj,1]

換句話說,只要能滿足上述4點,它就是對象;例如,類也是對象(類也是由type實例化產生的)

class Foo:
    pass

class Bar:
    pass

print(type(Foo))
print(type(Bar))
# 運行結果:
# <class ‘type‘>
# <class ‘type‘>

定義: 產生類的類稱之為元類,默認所有用class定義的類,他們的元類都是type

定義類的兩種方式:

  方式一:class方法(class方式也是利用了type這個元類)

  方式二:直接利用type這個元類的方法

定義類的三要素: 類名、類的基類、類的名稱空間

"""利用class方法"""
class Chinese:
    country = "China"

    def __init__(self,name,age):
        self.name = name
        self.age = age

    def talk(self):
        
print("%s is talking" %self.name) """利用type自己定義一個類""" class_name = "Chinese" # 類名 class_base = (object,) # 它的基類;元祖的形式 # 類體(即 類內部的代碼) class_body = """ country = "China" def __init__(self,name,age): self.name = name self.age = age def talk(self): print("%s is talking" %self.name) """
class_dict = {} # 類的名稱空間 exec(class_body,globals(),class_dict) # 執行類體的代碼,並把類體的值放入到 class_dict中作為其名稱空間 print(class_dict) # 打印結果: # {‘country‘: ‘China‘, ‘__init__‘: <function __init__ at 0x000000D574701E18>, ‘talk‘: <function talk at 0x000000D57487BD90>} # 定義類 Chinese1 = type(class_name,class_base,class_dict) # 這樣就定義出了一個類 print(Chinese) print(Chinese1) # 打印結果: # <class ‘__main__.Chinese‘> # <class ‘__main__.Chinese‘> obj = Chinese("neo",18) obj1 = Chinese1("egon",22) print(obj,obj.name,obj.age) print(obj1,obj1.name,obj1.age) # 打印結果: # <__main__.Chinese object at 0x000000F1D54B67B8> neo 18 # <__main__.Chinese object at 0x000000F1D54B6710> egon 22

自定義元類控制類的行為:

利用class定義類(如上面的 Chinese)時,完整的寫法其實是: class Chinese(object,metaclass = type) # metaclass = type的含義是 元類是type ; “class Chinese:” 這行代碼在執行(即定義Chinese類)時,其實是 Chinese = type(...) ,也就是 type這個元類在實例化。

如下代碼:

"""不以type作為元類,而以自己定義的類作為元類,來控制類的行為"""

"""第二步的分析:創建 Mymeta 元類"""
class Mymeta(type):  # class 是調用type這個元類; type元類裏面已經定義了很多的方法,而我只是重新定義其中一小部分代碼,所以我創建的元類需要繼承type,從而Mymeta能夠調用type的其他方法
    """
    Chinese 的元類是這個Mymeta,在定義Chinese類的時候需要實例化Mymeta,
    如: Chinese = Mymeta(class_name,class_bases,class_dict)
    需要往Mymeta中傳入定義類的三要素,所以 Mymeta中需要有 __init__ 方法
    """
    def __init__(self,class_name,class_bases,class_dict):  # 這個實例化的__init__ 肯定有這三個參數:class_name,class_bases,class_dict
        super(Mymeta,self).__init__(class_name,class_bases,class_dict)   # type 的 __init__ 方法中有很多的功能,自己創建的元類 Mymeta 首先需要繼承type的__init__ , 同時再定義自己的方法

        print(class_name)
        print(class_bases)
        print(class_dict)


"""第一步的分析:不以type作為元類,而以我自己創建的類 Mymeta 作為元類;所以我需要先定義一個元類 Mymeta"""
class Chinese(object,metaclass=Mymeta):
    country = "China"

    def __init__(self,name,age):
        self.name = name
        self.age = age

    def talk(self):
        print("%s is talking" %self.name)   # 類體代碼會以字典的形式傳入到class_dict中作為 Chinese類的名稱空間

# 運行結果:
# Chinese
# (<class ‘object‘>,)
# {‘__module__‘: ‘__main__‘, ‘__qualname__‘: ‘Chinese‘, ‘country‘: ‘China‘, ‘__init__‘: <function Chinese.__init__ at 0x00000075D7EDBD90>, ‘talk‘: <function Chinese.talk at 0x00000075D7EDBE18>}

"""執行上述代碼,直接就執行了 Mymeta 中 __init__ 的三個print,是因為 class Chinese(object,metaclass=Mymeta) 就是Mymeta在進行實例化,而實例化時會自動調用 __init__"""

補充知識點: raise TypeError("類型錯誤") # raise 是主動拋出異常,程序就不往下走了

自定義元類控制類的行為示例2:(類名首字母大寫)

class Mymeta(type):
    def __init__(self,class_name,class_bases,class_dict):
        """目標功能:如果類名的首字母沒有大寫,直接報錯"""
        if not class_name.istitle():  # .istitle() 用於判斷字符串首字母是否大寫,返回bool值
            raise TypeError("類名首字母必須大寫")  # 首字母沒有大寫,直接拋出異常,程序終止
        super(Mymeta,self).__init__(class_name,class_bases,class_dict)

class chinese(metaclass=Mymeta):  # 首字母沒有大寫  # 元類是 Mymeta
    pass

# 執行結果:
# 直接報錯
# TypeError: 類名首字母必須大寫

自定義元類控制類的行為示例2:(類體中需要有註釋且註釋不能為空)

補充知識: 類的名稱空間中有一個key 是 “__doc__”,它的含義是“類裏面的註釋”

class Mymeta(type):
    def __init__(self,class_name,class_base,class_dict):
        if "__doc__" not in class_dict or not class_dict["__doc__"].strip():  # class_dict 中沒有"__doc__"這個key 或者 class_dict["__doc__"]為空 # 空字符串和None自帶bool值為False
            raise TypeError("必須有註釋,且註釋不能為空")

        super().__init__(class_name,class_base,class_dict)

class chinese(metaclass=Mymeta):
    pass

# 運行結果:
# 報錯
# TypeError: 必須有註釋,且註釋不能為空

自定義元類控制類的實例化行為:

補充知識點: __call__ 方法: 讓對象(如 people)也能有 people()的方法。(此方法是給對象用的)

class Foo:
    def __call__(self, *args, **kwargs):  # 能讓對象變成一個可調用對象  # obj()能觸發 __call__
        print(self)
        print(args)
        print(kwargs)

obj = Foo()  # 同理,Foo()能這樣調用, Foo的那個元類(type)裏面也有 __call__ 方法
obj(1,2,3,a="A",b="AB")  # obj也能用類似於類Foo()的方法

# 運行結果:
# <__main__.Foo object at 0x000000D889D0A240>
# (1, 2, 3)
# {‘a‘: ‘A‘, ‘b‘: ‘AB‘}

"""
元類type內部也應該有一個 __call__ 方法, 會在調用Foo時觸發執行
Foo(1,2,x=1) 即相當於 Foo.__call__(Foo,1,2,x=1) 同時,類的調用也是一個實例化的過程
"""

自定義元類控制類的實例化:

class Mymeta(type):
    def __init__(self,class_name,class_bases,class_dict):
        if not class_name.istitle():
            raise TypeError("類的首字母必須大寫")
        if "__doc__" not in class_dict or not class_dict["__doc__"].strip():
            raise TypeError("類中必須有註釋且註釋不能為空")
     super(Mymeta,self).__init__(class_name,class_bases,class_dict)
"""現在我自己定義一個__call__ 方法""" def __call__(self, *args, **kwargs): print("=====>>") class Chinese(object,metaclass=Mymeta): """ xx """ def __init__(self,name,age): self.name = name self.age = age def talk(self): print("%s is talking" %self.name) # obj = Chinese("neo",22) # Chinese能這樣調用說明Mymeta中默認有 __call__ 方法 obj = Chinese("neo",22) # Chinese("neo",22) 在實例化的過程中會自動調用我重新定義的__call__ , 即 Chinese.__call__(Chinese,"neo",22) print(obj) # 運行結果: # =====>> # None """ 正常情況下,類實例化時會有3個過程發生: 1. 先造出一個空對象 2. 根據__init__把空對象初始化 3. 得到一個返回值 """ # 所以我上面在Mymeta中自定義的__call__ 是不規範的

下面對 Mymeta 中的 __call__ 方法進行修改,並說明類在實例化時自動調用其__init__方法的機制:

class Mymeta(type):
    def __init__(self,class_name,class_bases,class_dict):     # 這個__init__ 控制的是 類(Mymeta)的實例化
        if not class_name.istitle():
            raise TypeError("類的首字母必須大寫")
        if "__doc__" not in class_dict or not class_dict["__doc__"].strip():
            raise TypeError("類中必須有註釋且註釋不能為空")
        super(Mymeta,self).__init__(class_name,class_bases,class_dict)

    def __call__(self, *args, **kwargs):  # 它控制的是類(Chinese)的調用
        print("=====>>")
        # 第一步: 先造一個空對象obj
        obj = object.__new__(self)  # object.__new__(cls)的作用是新建一個 cls類 的對象
        # 第二步: 把空對象obj初始化
        self.__init__(obj,*args,**kwargs)  # Chinese調用自己的 __init__  # 把新建的obj這個對象傳入到 Chinese類的__init__ 中 # 這行的 *args和**kwargs的含義是: __call__中的*args和**kwargs是怎麽接收的,就怎麽原封不動的傳給這行代碼中的*args和*kwargs
        # 第三步: 返回obj
        return obj
    """
    以上也是類(Chinese)在實例化時__init__被觸發自動動用的原因,因為 __call__ 被自動調用,而 __call__ 中又手動調用了 類(Chinese)中的 __init__
    """

class Chinese(object,metaclass=Mymeta):
    """
    Chinese中的 __init__ 在被 Mymeta 中的 __call__ 調用時,*args中的"neo"當做位置參數傳給 name,**kwargs中的 age=22 當作關鍵字參數傳給 age
    """
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def talk(self):
        print("%s is talking" %self.name)

obj = Chinese("neo",age=22)
print(obj.__dict__)
# 運行結果:
# =====>>
# {‘name‘: ‘neo‘, ‘age‘: 22}

自定義元類控制類的實例化行為的應用:

單例模式: 如果生成的多個對象裏面的屬性完全一樣,就沒必要實例化多次,讓這多個對象使用同一塊內存空間就行

單例模式實現方式一 :

"""單例模式:讓實例化的的對象共用同一個內存地址"""

class Mysql:
    __instance = None  # 設置一個隱藏屬性,用於儲存實例化的對象
    def __init__(self):
        self.host = "127.0.0.1"
        self.port = 3306

    @classmethod
    def singleton(cls):
        if not cls.__instance:  # __instance 為 None時,說明還沒有實例化過
            obj = cls()  # 實例化得到一個對象
            cls.__instance = obj # 把實例化的對象賦值給 __instance
        return cls.__instance

    def con(self):
        pass

    def execute(self):
        pass

obj1 = Mysql.singleton()
obj2 = Mysql.singleton()
obj3 = Mysql.singleton()

print(obj1)
print(obj2)
print(obj3)

# 運行結果:
# <__main__.Mysql object at 0x000000E52BF366D8>
# <__main__.Mysql object at 0x000000E52BF366D8>
# <__main__.Mysql object at 0x000000E52BF366D8>

單例模式實現方式二: 元類的方式

"""通過自定義元類控制類的實例化過程:單例模式的 __call__ 方法"""

class Mymeta(type):
    __instance = None  # 定義一個隱藏屬性用於儲存實例化的對象
    def __init__(self,class_name,class_bases,class_dict):
        if not class_name.istitle():
            raise TypeError("首字母必須大寫,且只能首字母大寫")
        super().__init__(class_name,class_bases,class_dict)

    def __call__(self, *args, **kwargs):
        if not self.__instance: # 如果沒有經過實例化
            obj = object.__new__(self)  # 創建一個 self 的新對象
            self.__init__(obj)  # 對新創建的 self的對象執行 Mysql中的 __init__ 方法(實例化)
            self.__instance = obj  # 把實例化後的對象賦值給 __instance
        return self.__instance


class Mysql(metaclass=Mymeta):
    def __init__(self):
        self.host = "127.0.0.1"
        self.port = 3306

    def con(self):
        pass
    def execute(self):
        pass

obj1 = Mysql()
obj2 = Mysql()
obj3 = Mysql()

print(obj1)
print(obj2)
print(obj3)
# 運行結果:
# <__main__.Mysql object at 0x0000001965AC67B8>
# <__main__.Mysql object at 0x0000001965AC67B8>
# <__main__.Mysql object at 0x0000001965AC67B8>

面向對象:元類、