1. 程式人生 > >16 元類

16 元類

wal name 方法 工作 父類 on() 實例 .html 類的創建

面向對象學習目錄

1 面向對象介紹

2 類、實例、屬性、方法詳解

3 面向過程與面向對象進一步比較

4 類與對象

5 屬性查找與綁定方法

6 小結

7 繼承與派生

8 組合

9 抽象類

10 多態

11 封裝

12 綁定方法與非綁定方法

13 內置方法(上)

14 內置方法(中)之描述符

15 內置方法(下)

16 元類


一 知識儲備

exec:三個參數   參數一:字符串形式的命令   參數二:全局作用域(字典形式),如果不指定,默認為globals()   參數三:局部作用域(字典形式),如果不指定,默認為locals() exec的使用
 1 #可以把exec命令的執行當成是一個函數的執行,會將執行期間產生的名字存放於局部名稱空間中
2 g={x:1,y:2} 3 l={} 4 5 exec(‘‘‘ 6 global x,z 7 x=100 8 z=200 9 10 m=300 11 ‘‘‘,g,l) 12 13 print(g) #{‘x‘: 100, ‘y‘: 2,‘z‘:200,......} 14 print(l) #{‘m‘: 300}

二 引子(類也是對象)

1 class Foo:
2   pass
3  
4 f1=Foo() #f1是通過Foo類實例化的對象

  python中一切皆是對象,類本身也是一個對象,當使用關鍵字class的時候,python解釋器在加載class的時候就會創建一個對象(這裏的對象指的是類而非類的實例),因而我們可以將類當作一個對象去使用,同樣滿足第一類對象的概念,可以:   把類賦值給一個變量   把類作為函數參數進行傳遞   把類作為函數的返回值   在運行時動態地創建類 上例可以看出f1是由Foo這個類產生的對象,而Foo本身也是對象,那它又是由哪個類產生的呢?
1
#type函數可以查看類型,也可以用來查看對象的類,二者是一樣的 2 print(type(f1)) # 輸出:<class ‘__main__.Foo‘> 表示,obj 對象由Foo類創建 3 print(type(Foo)) # 輸出:<type ‘type‘>

三 什麽是元類?

  元類是類的類,是類的模板,元類是用來控制如何創建類的,正如類是創建對象的模板一樣,而元類的主要目的是為了控制類的創建行為   元類的實例化的結果為我們用class定義的類,正如類的實例為對象(f1對象是Foo類的一個實例,Foo類是 type 類的一個實例)   type是python的一個內建元類
,用來直接控制生成類,python中任何class定義的類其實都是type類實例化的對象 技術分享圖片

四 創建類的兩種方式

方式一:使用class關鍵字
1 class Chinese(object):
2     country=China
3     def __init__(self,name,age):
4         self.name=name
5         self.age=age
6     def talk(self):
7         print(%s is talking %self.name)

方式二:就是手動模擬class創建類的過程——將創建類的步驟拆分開,手動去創建
 1 #準備工作:
 2  
 3 #創建類主要分為三部分
 4  
 5   1 類名
 6  
 7   2 類的父類
 8  
 9   3 類體
10  
11  
12 #類名
13 class_name=Chinese
14 #類的父類
15 class_bases=(object,)
16 #類體
17 class_body="""
18 country=‘China‘
19 def __init__(self,name,age):
20     self.name=name
21     self.age=age
22 def talk(self):
23     print(‘%s is talking‘ %self.name)
24 """

步驟一(先處理類體->名稱空間):類體定義的名字都會存放於類的名稱空間中(一個局部的名稱空間),我們可以事先定義一個空字典,然後用exec去執行類體的代碼(exec產生名稱空間的過程與真正的class過程類似,只是後者會將__開頭的屬性變形),生成類的局部名稱空間,即填充字典
1 class_dic={}
2 exec(class_body,globals(),class_dic)
3  
4  
5 print(class_dic)
6 #{‘country‘: ‘China‘, ‘talk‘: <function talk at 0x101a560c8>, ‘__init__‘: <function __init__ at 0x101a56668>}

步驟二:調用元類type(也可以自定義)來產生類Chinense
 1 Foo=type(class_name,class_bases,class_dic) #實例化type得到對象Foo,即我們用class定義的類Foo
 2  
 3  
 4 print(Foo)
 5 print(type(Foo))
 6 print(isinstance(Foo,type))
 7 ‘‘‘
 8 <class ‘__main__.Chinese‘>
 9 <class ‘type‘>
10 True
11 ‘‘‘

我們看到,type 接收三個參數:
  • 第 1 個參數是字符串 ‘Foo’,表示類名
  • 第 2 個參數是元組 (object, ),表示所有的父類
  • 第 3 個參數是字典,這裏是一個空字典,表示沒有定義的屬性和方法
補充:若Foo類有繼承,即class Foo(Bar):.... 則等同於type(‘Foo‘,(Bar,),{})

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

  一個類沒有聲明自己的元類,默認他的元類就是type,除了使用元類type,用戶也可以通過繼承type來自定義元類(順便我們也可以瞅一瞅元類如何控制類的行為,工作流程是什麽) egon5步帶你學會元類(轉自老男孩教育egon老師)
#知識儲備:
    #產生的新對象 = object.__new__(繼承object類的子類)
 
#步驟一:如果說People=type(類名,類的父類們,類的名稱空間),那麽我們定義元類如下,來控制類的創建
class Mymeta(type): # 繼承默認元類的一堆屬性
    def __init__(self, class_name, class_bases, class_dic):
        if __doc__ not in class_dic or not class_dic.get(__doc__).strip():
            raise TypeError(必須為類指定文檔註釋)
 
        if not class_name.istitle():
            raise TypeError(類名首字母必須大寫)
 
        super(Mymeta, self).__init__(class_name, class_bases, class_dic)
 
 
class People(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)
 
#步驟二:如果我們想控制類實例化的行為,那麽需要先儲備知識__call__方法的使用
class People(object,metaclass=type):
    def __init__(self,name,age):
        self.name=name
        self.age=age
 
    def __call__(self, *args, **kwargs):
        print(self,args,kwargs)
 
 
# 調用類People,並不會出發__call__
obj=People(egon,18)
 
# 調用對象obj(1,2,3,a=1,b=2,c=3),才會出發對象的綁定方法obj.__call__(1,2,3,a=1,b=2,c=3)
obj(1,2,3,a=1,b=2,c=3) #打印:<__main__.People object at 0x10076dd30> (1, 2, 3) {‘a‘: 1, ‘b‘: 2, ‘c‘: 3}
 
#總結:如果說類People是元類type的實例,那麽在元類type內肯定也有一個__call__,會在調用People(‘egon‘,18)時觸發執行,然後返回一個初始化好了的對象obj
 
#步驟三:自定義元類,控制類的調用(即實例化)的過程
class Mymeta(type): #繼承默認元類的一堆屬性
    def __init__(self,class_name,class_bases,class_dic):
        if not class_name.istitle():
            raise TypeError(類名首字母必須大寫)
 
        super(Mymeta,self).__init__(class_name,class_bases,class_dic)
 
    def __call__(self, *args, **kwargs):
        #self=People
        print(self,args,kwargs) #<class ‘__main__.People‘> (‘egon‘, 18) {}
 
        #1、實例化People,產生空對象obj
        obj=object.__new__(self)
 
 
        #2、調用People下的函數__init__,初始化obj
        self.__init__(obj,*args,**kwargs)
 
 
        #3、返回初始化好了的obj
        return obj
 
class People(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)
 
obj=People(egon,18)
print(obj.__dict__) #{‘name‘: ‘egon‘, ‘age‘: 18}
 
#步驟四:
class Mymeta(type): #繼承默認元類的一堆屬性
    def __init__(self,class_name,class_bases,class_dic):
        if not class_name.istitle():
            raise TypeError(類名首字母必須大寫)
 
        super(Mymeta,self).__init__(class_name,class_bases,class_dic)
 
    def __call__(self, *args, **kwargs):
        #self=People
        print(self,args,kwargs) #<class ‘__main__.People‘> (‘egon‘, 18) {}
 
        #1、調用self,即People下的函數__new__,在該函數內完成:1、產生空對象obj 2、初始化 3、返回obj
        obj=self.__new__(self,*args,**kwargs)
 
        #2、一定記得返回obj,因為實例化People(...)取得就是__call__的返回值
        return obj
 
class People(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)
 
    def __new__(cls, *args, **kwargs):
        obj=object.__new__(cls)
        cls.__init__(obj,*args,**kwargs)
        return obj
 
obj=People(egon,18)
print(obj.__dict__) #{‘name‘: ‘egon‘, ‘age‘: 18}
 
#步驟五:基於元類實現單例模式,比如數據庫對象,實例化時參數都一樣,就沒必要重復產生對象,浪費內存
class Mysql:
    __instance=None
    def __init__(self,host=127.0.0.1,port=3306):
        self.host=host
        self.port=port
 
    @classmethod
    def singleton(cls,*args,**kwargs):
        if not cls.__instance:
            cls.__instance=cls(*args,**kwargs)
        return cls.__instance
 
 
obj1=Mysql()
obj2=Mysql()
print(obj1 is obj2) #False
 
obj3=Mysql.singleton()
obj4=Mysql.singleton()
print(obj3 is obj4) #True
 
#應用:定制元類實現單例模式
class Mymeta(type):
    def __init__(self,name,bases,dic): #定義類Mysql時就觸發
        self.__instance=None
        super().__init__(name,bases,dic)
 
    def __call__(self, *args, **kwargs): #Mysql(...)時觸發
 
        if not self.__instance:
            self.__instance=object.__new__(self) #產生對象
            self.__init__(self.__instance,*args,**kwargs) #初始化對象
            #上述兩步可以合成下面一步
            # self.__instance=super().__call__(*args,**kwargs)
 
        return self.__instance
class Mysql(metaclass=Mymeta):
    def __init__(self,host=127.0.0.1,port=3306):
        self.host=host
        self.port=port
 
 
obj1=Mysql()
obj2=Mysql()
 
print(obj1 is obj2)

六 練習題

練習一:在元類中控制把自定義類的數據屬性都變成大寫
 1 class Mymetaclass(type):
 2     def __new__(cls,name,bases,attrs):
 3         update_attrs={}
 4         for k,v in attrs.items():
 5             if not callable(v) and not k.startswith(__):
 6                 update_attrs[k.upper()]=v
 7             else:
 8                 update_attrs[k]=v
 9         return type.__new__(cls,name,bases,update_attrs)
10  
11 class Chinese(metaclass=Mymetaclass):
12     country=China
13     tag=Legend of the Dragon #龍的傳人
14     def walk(self):
15         print(%s is walking %self.name)
16  
17  
18 print(Chinese.__dict__)
19 ‘‘‘
20 {‘__module__‘: ‘__main__‘,
21 ‘COUNTRY‘: ‘China‘,
22 ‘TAG‘: ‘Legend of the Dragon‘,
23 ‘walk‘: <function Chinese.walk at 0x0000000001E7B950>,
24 ‘__dict__‘: <attribute ‘__dict__‘ of ‘Chinese‘ objects>,
25 ‘__weakref__‘: <attribute ‘__weakref__‘ of ‘Chinese‘ objects>,
26 ‘__doc__‘: None}
27 ‘‘‘

練習二:在元類中控制自定義的類無需init方法 1.元類幫其完成創建對象,以及初始化操作;   2.要求實例化時傳參必須為關鍵字形式,否則拋出異常TypeError: must use keyword argument 3.key作為用戶自定義類產生對象的屬性,且所有屬性變成大寫
 1 class Mymetaclass(type):
 2     # def __new__(cls,name,bases,attrs):
 3         # update_attrs={}
 4         # for k,v in attrs.items():
 5             # if not callable(v) and not k.startswith(‘__‘):
 6                 # update_attrs[k.upper()]=v
 7             # else:
 8                 # update_attrs[k]=v
 9         # return type.__new__(cls,name,bases,update_attrs)
10  
11     def __call__(self, *args, **kwargs):
12         if args:
13             raise TypeError(must use keyword argument for key function)
14         obj = object.__new__(self) #創建對象,self為類Foo
15  
16         for k,v in kwargs.items():
17             obj.__dict__[k.upper()]=v
18         return obj
19  
20 class Chinese(metaclass=Mymetaclass):
21     country=China
22     tag=Legend of the Dragon #龍的傳人
23     def walk(self):
24         print(%s is walking %self.name)
25  
26  
27 p=Chinese(name=egon,age=18,sex=male)
28 print(p.__dict__)

16 元類