Python之元類詳細解析


一、補充內置函數isinstance和issubclass

1、isinstance是判斷一個對象是不是由一個對象產生的

 1 class Foo:
 2     pass
 3 
 4 obj=Foo()
 5 
 6 print(isinstance(obj,Foo))        #判斷一個對象是否是由某個類調用產生
 7 
 8 # 在python3中統一類與類型的概念
 9 d={'x':1} #d=dict({'x':1} #)    #類即類型,d是一個對象,dict是一個類,d是由調用dict產生的對象
10 
11 print(type(d) is dict)          #type也可以判斷數據類型,但這並不是type的的主要功能,其實type是一個元類
12 print(isinstance(d,dict))  

 

2、issubclass是判斷一個l類是不是另外一個類的子類

# issubclass()
class Parent:
    pass

class Sub(Parent):
    pass

print(issubclass(Sub,Parent))        #判斷類Sub是不是Parent是不是的子類,是布爾值為True
print(issubclass(Parent,object))

 二、反射

'''
1、什么是反射
    通過字符串來操作類或者對象的屬性

2、如何用
    hasattr
    getattr
    setattr
    delattr


'''

class People:
    country='China'
    def __init__(self,name):
        self.name=name

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

peo1=People('egon')
# hasattr:判斷-------(對象)---------中有沒有一個------(字符串形式的屬性)-------對應的數據屬性或函數屬性,結果是一個bool值
print(hasattr(peo1,'eat')) #效果等價:peo1.eat #

# getattr:判斷-------(對象)---------中有沒有一個------(字符串形式的屬性)-------對應的數據屬性或函數屬性,如果有對應的數據屬性,返回的是數據屬性對應的值,如果有對應的函數屬性返回的是一個內存地址
print(getattr(peo1,'eat')) #peo1.eat      #類中有這個屬性,返回的是一個內存地址
print(getattr(People,'country'))          #類中有數據屬性,返回的是數據屬性對應的屬性值
print(getattr(peo1,'xxxxx',None))         #None是一個默認值,如果類中沒有這個數據屬性、函數屬性就會將默認值返回,如果沒有默認值,類中也沒有字符串對應的屬性,就會報錯

# setattr:判斷-------(對象)---------中有沒有一個------(字符串)-------對應的數據屬性,有可以對其屬性值進行修改,沒有添加新的屬性
setattr(peo1,'age',18) #peo1.age=18   #可以給對象添加新的屬性
setattr(peo1,'name','alex')           #可以修改對象的屬性值
print(peo1.age)
print(peo1.name)

# print(peo1.__dict__)
# delattr:判斷-------(對象)---------中有沒有一個------(字符串形式的屬性)-------對應的數據屬性或函數屬性,有可以將其刪除
delattr(peo1,'name') #del peo1.name
print(peo1.__dict__)
delattr(peo1,'eat')        #-----會報錯,setattr和delattr只會對自己名稱空間的名字做修改和刪除,並不能改變類名稱空間中的屬性名


'''重點'''
# 反射的應用:
class Ftp:
    def __init__(self,ip,port):
        self.ip=ip
        self.port=port

    def get(self):
        print('GET function')

    def put(self):
        print('PUT function')

    def run(self):
        while True:
            choice=input('>>>: ').strip()       #模擬用戶實際輸入對應的命令是一個字符串,所以此時就要用到映射
            '''方法一'''
            # # print(choice,type(choice))
            # if hasattr(self,choice):             #判斷用戶輸入的字符串形式的屬性名是否存在,拿到一個返回的bool值
            #     method=getattr(self,choice)      #輸入的字符串形式的屬性名存在,通過屬性名拿到綁定方法的內存地址
            #     method()                         #綁定方法加括號即調用
            # else:
            #     print('輸入的命令不存在')

            '''方法二'''
            method=getattr(self,choice,None)       #如果用戶輸入的字符串形式的屬性值存在,則直接拿到該屬性的綁定方法的內存地址返回,屬性是不存在,返回None
            if method is None:
                print('輸入的命令不存在')
            else:
                method()                           #函數屬性的內存地址加括號直接調用

conn=Ftp('1.1.1.1',23)                             #類加括號,產生一個空對象,並對對象通過__init__進行初始化
conn.run()                                         #將對象綁定給方法,會將對象當做第一個參數自動傳入

 三、自定義方法來定義類的功能

1、__str__方法-------會在打印對象的時候自動觸發
class People:
    def __init__(self,name,age):
        self.name=name
        self.age=age

    #在對象被打印時,自動觸發,應該在該方法內采集與對象self有關的信息,然后拼成字符串返回
    def __str__(self):
        # print('======>')
        return '<name:%s age:%s>' %(self.name,self.age)     #返回的必須是字符串,否則就會報錯
obj=People('egon',18)
obj1=People('alex',18)
print(obj)                  #obj.__str__()        #沒有__str__打印對象的結果就是一個內存地址,
                            # 但是有了__str__我們就可以拼接任意我們想要的樣式,此時在打印對象就會得到我們拼接的結果
print(obj)  #obj.__str__()
print(obj)  #obj.__str__()
print(obj1)  #obj1.__str__()

""" 默認就有str,只是返回的是內存地址,我們可以通過設置str,可以通過打印對象打印出好看的格式,而不是只是單純的打印出一個內存地址"""

d={'x':1} #d=dict({'x':1})
print(d)  #打印d這個對象是一個字典,而不是像上面一樣是一個內存地址,是因為dict內一定自帶了一個__str__,在打印對象的時候自動觸發
 
        

2、__del__會在對象被刪除時自動觸發
# """1、__del__析構方法"""
# # __del__會在對象被刪除時自動觸發
class People:
    def __init__(self,name,age):
        self.name=name
        self.age=age
        self.f=open('a.txt','rt',encoding='utf-8')      #打開該文件占用兩方面的資源,一是應用程序即Python解釋器,Python會自動回收,另一方面是系統資源,要手動關閉

    def __del__(self):               #刪除對象是自動觸發
        print('run=-====>')
        # 做回收系統資源相關的事情
        self.f.close()            #刪除應用程序資源要在關閉系統資源之后,要不就會出現系統資源無法關閉,白白占用資源

obj=People('egon',18)

print('')             #系統程序運行完畢,回收資源,會自動觸發__del__的執行,所以先打印:'主',再打印:'run=-====>'

四、元類

'''
1、什么是元類
    在python中一切皆對象,那么我們用class關鍵字定義的類本身也是一個對象
    負責產生該對象的類稱之為元類,即元類可以簡稱為類的類

    class Foo: # Foo=元類()        #一切皆對象,類加括號產生對象
        pass
2、為何要用元類
    元類是負責產生類的,所以我們學習元類或者自定義元類的目的
    是為了控制類的產生過程,還可以控制對象的產生過程

3、如何用元類

'''
#1、儲備知識:內置函數exec的用法
cmd="""
x=1
def func(self):
    pass
"""
class_dic={}
exec(cmd,{},class_dic)     #exec會將cmd字符串中的代碼拿出來執行一次,將產生的名字丟掉事先定義好的class_dic空字典中

print(class_dic)            #{'x': 1, 'func': <function func at 0x00000267165F92F0>}


#2、創建類的方法有兩種
# 大前提:如果說類也是對象的話,那么用class關鍵字去創建類的過程也是一個實例化的過程
# 該實例化的目的是為了得到一個類,調用的是元類
#2.1 方式一:用的默認的元類type
class People: #People=type(...)--------默認的元類type實例化出一個對象Pelple,實例化的結果也是一個對象
    country='China'
    def __init__(self,name,age):
        self.name=name
        self.age=age

    def eat(self):
        print('%s is eating' %self.name)
peo=People('EGON',18)
print(peo)               #------------<__main__.People object at 0x000001F635282E10>*********調用類實例化出對象
print(type(People))      #------------<class 'type'>*****************************************調用元類實例化出類



"""重點"""
#2.1.1 創建類的3個要素:類名,基類,類的名稱空間
class_name='People'         #類名,是一個字符串,---------由上面的class定義類我們知道,創建類的三要素:類名,基類,類的名稱空間
class_bases=(object,)       #基類,----------------------我們通過__bases__,知道基類是一個元組的形式
class_dic={}                #類的名稱空間,---------------通過__dict__,知道類的名稱空間的是一個字典
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(class_body,{},class_dic)#執行字符傳中的代碼,將產生的名字方到class_dic的名稱空間中,即之前定義類將產生的名字放到類的名稱空間中

# 准備好創建類的三要素
# print(class_name)        #-------People
# print(class_bases)       #-------(<class 'object'>,)
# print(class_dic)         #-------{'country': 'China', '__init__': <function __init__ at 0x00000222D55F92F0>, 'eat': <function eat at 0x00000222DC618BF8>}

# People=type(類名,基類,類的名稱空間)                #調用元類就可以產生一個類這個對象
People1=type(class_name,class_bases,class_dic)      #將事先定義好的類的三要素放到當做參數傳給元類,調用元類即產生對象
print(People1)             #--------<class '__main__.People'>自定義類產生的結果
obj1=People1('egon',18)
print(People)              #--------<class '__main__.People'>,class定義類產生的結果
obj=People('egon',18)

obj1.eat()
obj.eat()


"""----------------------------------------重點----------------------------------------"""
#2.2 方式二:用的自定義的元類
class Mymeta(type): #只有繼承了type類才能稱之為一個元類,否則就是一個普通的自定義類
    def __init__(self,class_name,class_bases,class_dic):
        print(self) #現在是People
        print(class_name)
        print(class_bases)
        print(class_dic)
        super(Mymeta,self).__init__(class_name,class_bases,class_dic) #重用父類的功能

# 分析用class自定義類的運行原理(而非元類的的運行原理):
#1、拿到一個字符串格式的類名class_name='People'
#2、拿到一個類的基類們class_bases=(obejct,)
#3、執行類體代碼,拿到一個類的名稱空間class_dic={...}------------------前三步就是造類的三要素
#4、調用People=type(class_name,class_bases,class_dic)----------------調用元類(類)產生類(對象)------------調用類產生對象
class People(object,metaclass=Mymeta): #People=Mymeta(類名,基類們,類的名稱空間)------metaclass=Mymeta是自定義的元類名
    country='China'
    def __init__(self,name,age):
        self.name=name
        self.age=age

    def eat(self):
        print('%s is eating' %self.name)
"""----------------------------------------重點----------------------------------------"""





"""應用:自定義元類控制類的產生過程,類的產生過程其實就是元類的調用過程------(對象的產生過程就是調用類的過程)"""
class Mymeta(type): #只有繼承了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('類名首字母必須大寫')
        super(Mymeta,self).__init__(class_name,class_bases,class_dic)                         #重用父類的功能

class People(object,metaclass=Mymeta): #People=Mymeta('People',(object,),{....})
    """這是People類"""
    country='China'
    def __init__(self,name,age):
        self.name=name
        self.age=age

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



#3 儲備知識:__call__
class Foo:
    def __call__(self, *args, **kwargs):
        print(self)      #<__main__.Foo object at 0x000002193E892E10>
        print(args)       #(1, 2, 3)----------------*args接收位置參數,存成元組的形式
        print(kwargs)     #{'x': 1, 'y': 2}---------**kwargs接收關鍵字參數,存成字典的形式


obj=Foo()          #調用類不會自動觸發,會在調用對象時自動觸發,通過self也可以看出,是調用對象時自動觸發

# # 要想讓obj這個對象變成一個可調用的對象,需要在該對象的類中定義一個方法__call__方法
# # 該方法會在調用對象時自動觸發
# obj(1,2,3,x=1,y=2) #調用對象時自動觸發__call__方法,並將對象自動傳入



"""-----------------------------------------------重點---------------------------------------------"""
# 4、自定義元類來控制類的調用的過程,即類的實例化過程
class Mymeta(type):

    def __call__(self, *args, **kwargs):          #會在調用對象時自動觸發,此時的對象時一個類,即People
        # print(self) # self是People
        # print(args)
        # print(kwargs)
        # return 123
        """調用類產生一個對象,發生兩件事"""       #和class定義類,調用類一樣發生兩件事
        # 1、先造出一個People的空對象
        obj=self.__new__(self)                   #造出了一個自定義類People的空對象
        # 2、為該對空對象初始化獨有的屬性
        # print(args,kwargs)
        self.__init__(obj,*args,**kwargs)        #對空對象進行初始化,空對象傳入,以及參數原封不動的傳入

        # 3、返回一個初始好的對象
        return obj                               #將造出的對象返回,

'''**********************************看成一個對象************************************************'''
class People(object,metaclass=Mymeta):           #自定義類People,元類是Mymeta,元類必須繼承type類,否則就不是元類
    country='China'
    def __init__(self,name,age):
        self.name=name
        self.age=age

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

    def __new__(cls, *args, **kwargs):           #對象自己中有__new__屬性,先從對象自己的名稱空間中找,自己沒有在到自己的類中找
        print(cls)
        # cls.__new__(cls) # 錯誤                 #自己有調用了自己的__new__,這樣就出現無線遞歸,所以會報錯
        obj=super(People,cls).__new__(cls)       #自己中有,我們任然讓其取繼承父類中的__new__屬性,來產生一個空對象,然后將對象初始化,拿到一個返回值
        return obj
'''**********************************看成一個對象************************************************'''

"""-----------------------------------------------重點---------------------------------------------"""



# 分析:調用Pepole的目的
#1、先造出一個People的空對象
#2、為該對空對象初始化獨有的屬性
# obj1=People('egon1',age=18)
# obj2=People('egon2',age=18)
# print(obj1)
# print(obj2)

# obj=People('egon',age=18)
# print(obj.__dict__)
# print(obj.name)
# obj.eat()

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM