1. 程式人生 > >元類介紹

元類介紹

# 元類
# 說python當中一切皆物件,
# 用class關鍵字定義的類其實本質也是一個物件。
# class Foo:
# pass
# 說Foo是一個物件的話,物件肯定是由一個類例項化而來的。
# 也就是說在class的時候,調class關鍵字的時候,必然是這個關鍵字去調了一個類的類,類的類就稱之為元類。
# 就是調了一個元類加括號例項化,元類加括號的結果就是用class定義的類
# class Foo: # Foo = 元類()
# pass
# 所以說元類就是負責產生類的那一個類,是類的老祖宗。

# 1.什麼是元類?
# 在python中一切皆物件,那麼我們用class關鍵字定義的類本身也是一個物件
# 負責產生該物件的類稱之為元類,即元類可以簡稱為類的類


# 2.為何要用元類?
# 什麼情況下用元類?
# 元類是負責產生類的,可以控制類的產生過程,還可以控制物件的產生過程。
# 什麼叫物件的產生過程?
# class Foo:
# pass
# Foo()
# 對Foo這個類的產生控制,以及對呼叫Foo的這個過程的控制,因為呼叫Foo的過程就是產生Foo物件的過程


# 3.如何用元類

# 3.1 需要儲備幾點知識
# 第一點知識: exec()這個內建函式

# exec()這個內建函式用來幹什麼的呢?
# 將一個字串中的程式碼提出出來執行一下

# cmd = '''
# x=1
# y=2
# print('我愛老婆楊靜,哈哈哈')
# '''
# local_dic = {}
# exec(cmd,)
# 模擬的就是執行python程式碼的過程,會產生一個名字,必然往名稱空間裡面去丟
# 名稱空間的格式是以字典的形式展現的
# 所以造兩個名稱空間,一個全域性名稱空間
# 怎麼檢視全域性的名稱空間:
# print(globals())
# 造一個空字典
# local_dic = {}
# exec(第一個引數是你的字串,第二個引數是你要引用的那個全域性作用域(不需要定義個空字典就好了),第三個是local_dic)
# exec(cmd,{},local_dic)
# print(local_dic)
# 會發生什麼是呢?
# exec會執行字串中的程式碼,將執行過程中產生的名字都往local_dic = {}裡面丟
# exec幹了一件事:給你一段程式碼,只要執行過程中產生名字都丟到一個名稱空間字典裡面去。
# 函式也可以!!
# 函式體程式碼提取出來
# def func():
# x= 1
# y=2
# print('楊靜愛老公張仁國,哈哈哈')

# cmd = '''
# x = 1
# y = 2
# print('楊靜愛老公張仁國,哈哈哈')
# '''
# func_dic = {}
# exec(cmd,{},func_dic)
#
# print(func_dic)

# 類也可以!!
# 類體程式碼提取出來

# cmd = '''
# x=1
# def func():
# y = 2
# '''
# class_dic = {}
# exec(cmd,{},class_dic)
# print(class_dic)
# 類體執行過程中產生的名字都丟到名稱空間裡面去了
# 這是模擬了類建立過程中類定義階段造名稱空間那一步操作


# 用這個功能幹嘛使?
# 可以用來模擬造名稱空間這個過程。


# 3.2建立類過程當中有幾個關鍵的要素
# 建立類的方法有兩種
# 第一種class關鍵字去建立,用的是預設的元類:type
# class People():
# country = 'china'
# def __init__(self,name,age):
# self.name = name
# self.age = age
#
# def eat(self):
# print('%s is eating'%self.name)

# # 檢視People的類是誰:
# print(type(People))
# # <class 'type'>
#
# print(People)
# obj = People('zrg',24)
# print(obj)
# obj.eat()
# print(type(obj))

# 大前提:如果說類也是物件的話,那麼用class關鍵字去建立類的過程也是一個例項化的過程
# 該例項化的目的是為了得到一個類,呼叫的是元類
# 呼叫的元類又分為兩種:
# 一種是預設的元類type
# 另一種是自定義的

# 預設的元類的造類的過程:
# 類的名字 class_name
# 類的基類 class_bases
# 類的名稱空間 class_dic
#
# class_name = 'People' #類名必須是字串形式
# class_bases = (object,) #不繼承任何類預設就是object逗號才叫一個元組
# class_dic = {} #最關鍵的最難得,類的名稱空間一個字典的形式
# 類的名稱空間從何而來----->從類體的執行而來,而類體就是一堆字串
# class_body = '''
# country = 'china'
# def __init__(self,name,age):
# self.name = name
# self.age = age
#
# def eat(self):
# print('%s is eating'%self.name)
# '''
# 要做的事根據類體程式碼得到字典讓他裡面有值,就用exec函式
# exec(class_body,{},class_dic)
# 檢視造類的三要素
# print(class_name)
# print(class_bases)
# print(class_dic)

# type(類名,基類,類的名稱空間)得的結果給一個變數名People1
# People1 = type(class_name,class_bases,class_dic)
# print(People1)
# obj1 = People1('yj',25)
# obj1.eat()


# 第二種 自定義元類
# 自定義的話就是把type換成自己寫的類名字

# 定義自己的元類不一定所有的功能都要自己寫,大部分都是源自type的所以繼承
# class Mymeta(type): #只有繼承了type類才能稱之為一個元類,否則就是一個普通的自定義的類
# def __init__(self,class_name,class_bases,class_dic):
#控制邏輯
# if class_dic.get('__doc__') is None or len(class_dic.get('__doc__').strip()) == 0:
# raise TypeError('類中必須有文件註釋,並且文件註釋不能為空')
# if not class_name.istitle():
# raise TypeError('類名首字母必須大寫')
# print(self) #現在是 People
# print(class_name)
# print(class_bases)
# print(class_dic)
# 上面的改寫的功能是把父類的覆蓋掉了,父類的可能還有些其他有用的程式碼
# 所以super括號括起來,自己的類名,逗號self就是People,然後括號__init__也需要把三個引數傳進去
# 這是保險起見把父類的功能也重用一下
# super(Mymeta,self).__init__(class_name,class_bases,class_dic)


# 分析class的執行原理:
# 1.拿到一個字串格式的類名 class_name
# 2.拿到一個類的基類們 class_bases
# 3.執行類體程式碼,拿到一個類的名稱空間 class_dic
# 4.呼叫type(class_name,class_bases,class_dic)將得到的結果賦值給個變數名People
# People = type(class_name,class_bases,class_dic)

# class People(object,metaclass=Mymeta): # People = Mymeta(類名,基類們,名稱空間)
#加文件註釋 不寫 為空都是不合法的
# '''這是一個People類'''
# country = 'china'
# def __init__(self,name,age):
# self.name = name
# self.age = age

# def eat(self):
# print('%s is eating'%self.name)

# print(People.__dict__) #看名稱空間
# 文件註釋是空
# {'__module__': '__main__', 'country': 'china', '__init__': <function People.__init__ at 0x000001E1BAFD9950>, 'eat': <function People.eat at 0x000001E1BAFD99D8>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None}
# 應用:自定義元類控制類的產生過程,類的產生過程其實就是元類的呼叫過程





# 3.3儲備知識 __call__
# __開頭__結尾 會在滿足某種條件時自動觸發執行
# class Foo:
# def __call__(self, *args, **kwargs):
# print('ok')
# print(self)
# print(args) #args就是傳的位置引數
# print(kwargs) #kwargs就是傳的關鍵字引數
#
# obj = Foo()
# # obj() #要想讓obj這個物件變成一個可呼叫的物件,需要早該物件的類中定義一個方法__call__方法
# # 該方法會在呼叫物件是自動觸發
# obj(1,2,3,x=1,y=2)



# 自定義元類控制類的產生過程
# 其實自定義類定義完了之後,還要去呼叫那個類,還想要去控制自定義類的那個呼叫過程
# 也就是自定義類創造物件的過程
# 4. 自定義元類來控制類的呼叫過程,即類的例項化過程

class Mymeta(type):
def __call__(self, *args, **kwargs):
# print(self) #self是People
# print(args)
# print(kwargs)
# 1.先造出一個People空物件
obj = self.__new__(self)
# 2.為該空物件初始化獨有的屬性
self.__init__(obj,*args,**kwargs)
# 3.返回一個初始化好的obj
return obj



class People(object,metaclass=Mymeta): # People = Mymeta(類名,基類們,名稱空間)
# 加文件註釋 不寫 為空都是不合法的
'''這是一個People類'''
country = 'china'
def __init__(self,name,age):
self.name = name
self.age = age

def eat(self):
print('%s is eating'%self.name)

# 分析:呼叫People的目的
# 1.先造出一個People空物件
# 2.為該空物件初始化獨有的屬性

obj = People('zrg',age=24)
print(obj.__dict__)
print(obj.name)
obj.eat()


# 調物件就是調物件內的call方法,調物件拿到的返回值就是call方法的返回值
# 不寫返回值就是一個None