元類

qq_52159647發表於2020-12-06

Python元類

一.引言:
python語言是一門物件導向的語言,在python中一切皆物件
二 類的兩種建立方式:
一個類的三大組層部分:
1.類名
2.父類
3.三類體程式碼
1.類的建立可以用type方法

#tyoe('類名',(父類,),{名稱空間})
classname = 'yct'

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

Person = type('Person',(object,),{classname:'yct','__init':__init__,'say':say})
print(Person.__dict__)

2.類的建立可以用class關鍵字

class Person():
    classname = 'yct'

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

    def say(self):
        pass

print(Person.__dict__)

三、高度化定製類的三種方法
1、new
在python建立類的過程中,會首先呼叫__new__方法返回一個obj物件:

class People():
    def __new__(cls, *args, **kwargs):
        print(cls)
        print(args)
        print(kwargs)
        obj = object.__new__(cls)  # 對於例項化一個類只接受物件的類
        print(obj.__dict__)  # 此時obj為空
        return obj  #返回obj物件給__init__方法使用

    def __init__(self,name,age):  # init為obj物件定製引數
        self.name = name
        self.age = age
people = People('egon',18)

正如程式碼所示:__new__創造出了物件,但是這個物件是空的,名稱空間的裝備需要通過__init__來完成。
例1:例項化物件的__new__方法
基於例項化物件的__new__方法會創造一個空物件,然後__init__方法進行武裝:

class People():
    def __new__(cls, *args, **kwargs):
        print(cls)
        print(args)
        print(kwargs)
        obj = object.__new__(cls)  # 對於例項化一個類只接受物件的類
        print(obj.__dict__)  # 此時obj為空
        return obj  #返回obj物件給__init__方法使用

    def __init__(self,name,age):  # init為obj物件定製引數
        self.name = name
        self.age = age
people = People('egon',18)

例2:基於建立類的元類__new__方法:
基於建立類的元類__new__方法,自動傳入類,同時會自動傳入類名,類體,和類的父類方法:

在這裡插入程式碼片class Mymeta(type):
    def __new__(cls, classname, father,cls_dict):
        print(cls_dict)
        if '__doc__'not in cls_dict:
            print('類必須要有註釋!')
            return
        # print(kwargs)
        obj =super().__new__(cls,classname,father,cls_dict)  # 在 type內部被呼叫創造了obj的有__dict__的物件
        # print(obj.__dict__)
        print(obj.__dict__)  #不為空
        return obj



class People(metaclass=Mymeta):
    '''天上有多少顆星星'''
    school = 'stars'

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

    def say(self):
        pass

總結:在對例項化物件時和對類的建立時區別:
1.例項化物件時會返回一個空物件,再交由__init__方法武裝空字典
2.建立類方法時會自動傳入類,類名,父類,名稱空間,type類__init__方法會自動進行武裝
例項:註釋

"""利用元類:第一:類必須有文件註釋,不能為空 第二:在一個類內部定義的所有函式必須有文件註釋,不能為空"""
寫法一:
class MyMetaClass(type):
    def __new__(cls, cls_name, cls_bases, cls_dict):
        print(cls)  #注意:<class '__main__.MyMetaClass'>
        if '__doc__' not in cls_dict or len(cls_dict['__doc__'].strip()) == 0:
            raise TypeError('類:%s 必須有文件註釋,並且註釋不能為空.' % cls_name)
        for key, value in cls_dict.items():
            if key.startswith('__'):
                continue
            if not callable(value):
                continue
            if not value.__doc__ or len(value.__doc__.strip()) == 0:
                raise TypeError('函式:%s 必須有文件註釋,並且文件註釋不能為空.' % (key))
        obj =  super().__new__(cls,cls_name,cls_bases,cls_dict)
        print(obj)  #<class '__main__.Person'>  建立出來的就是Person類,是我們需要的.
        print('--------名稱空間------')
        print(obj.__dict__)
        return obj

    def __init__(self,cls_name, cls_bases, cls_dict):
        print(self)   #<class '__main__.Person'>
        #接下來我們初始化這個物件
        super().__init__(cls_name,cls_bases,cls_dict)



class Person(object,metaclass=MyMetaClass):  #這一行: Person = MyMetaClass('Person',(object,),{...})
    """
    元類.
    """
    country = 'China'

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

    def tell_info(self):
        """
        繫結方法.
        """
        print('%s 的年齡是:%s'%(self.name,self.age))
方法二:
class Mymeta(type):
    def __new__(cls, classname, father,cls_dict):
        print(cls_dict)
        if '__doc__'not in cls_dict or cls_dict['__doc__'] is None:
            raise TypeError('類的建立必需要有註釋')
        for key,values in cls_dict.items():
            if callable(values):
                if values.__doc__ is None:
                    raise TypeError('方法下必要要有註釋')

        obj =super().__new__(cls,classname,father,cls_dict)  
        # print(obj.__dict__)
        print(obj.__dict__)  
        return obj


class People(metaclass=Mymeta):
    '''類下注釋'''
    school = 'stars'

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

    def say(self):
        pass

2.、init (__init__方法)

(暫時省略)
3、call (元類對類例項化行為的影響):
1.一個可呼叫物件加括號實際上是呼叫對物件下的__call__方法,反之一個可以呼叫的物件,他的類中必有__call__方法.
2.要想讓某個物件變成一個可呼叫的物件,需要在該物件的類中定義一個方法__call__方法。

例1.例項化物件

class Person(object):
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def __call__(self, *args, **kwargs):  # ==>給例項化物件使用
        print('呼叫我')


student = Person('ww',199)
"""如果沒有__call__:TypeError: 'Student' object is not callable"""
print(callable(student))
print(student())  #一個可呼叫的物件加括號,就是觸發這個物件所在類中的__call__方法的執行

在此處,我們可以明白,如果類可以被呼叫,則元類中必然存在一個__call__方法!!!

class Mymeta(type):
    pass

class People(metaclass=Mymeta):

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

    def say(self):
        pass
print(People.__dict__)
# 在Mymeta中必然有一個__call__方法,如果沒有定義則自動呼叫父類

例2:重寫Type類方法

class Mymeta(type):
    def __call__(self, *args, **kwargs):
        print(self)
        print(args)
        print(kwargs)
        return 1

class People(metaclass=Mymeta):

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

    def say(self):
        pass
people = People('egon',18)
print(people)

此處我們可以發現call方法會將實參傳入,其返回值就是people的返回值,在此處people = 1
所以在此我們可以下一個結論,__call__是對類的例項化進行管控,對例項化物件字典的增刪改查就是在call內部進行的,最後返回了一個字典。此字典就是物件下的屬性

PYTHON中,call,init,__new__三者之間的關係
一個類在例項化的時候實際上是做了三件事情:
1.在呼叫產生類時會自動呼叫__call__類
2.在__call__呼叫中會呼叫類物件自己中的__new__方法
3.在__new__方法返回物件後自動呼叫__init__方法
4.將得到的obj結果返回給物件

相關文章