面向對象高階-14元類

一丶什麼是元類

  • 在python中一切皆對象,那麼咱們用class關鍵字定義的類自己也是一個對象,負責產生該對象的類稱之爲元類,即元類能夠簡稱爲類的類
class Foo:  # Foo=元類()
    pass

二丶爲何用元類

元類是負責產生類的,因此咱們學習元類或者自定義元類的目的:是爲了控制類的產生過程,還能夠控制對象的產生過程

三丶內置函數exec

cmd = """
x=1
print('exec函數運行了')
def func(self):
    pass
"""
class_dic = {}
# 執行cmd中的代碼,而後把產生的名字丟入class_dic字典中
exec(cmd, {}, class_dic)

exec函數運行了python

print(class_dic)

{'x': 1, 'func': <function func at 0x10a0bc048>}函數

四丶class建立類

  • 若是說類也是對象,那麼用class關鍵字的去建立類的過程也是一個實例化的過程,該實例化的目的是爲了獲得一個類,調用的是元類
  • 用class關鍵字建立一個類,用的默認的元類type,所以之前說不要用type做爲類別判斷
class People:  # People=type(...)
    country = 'China'

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

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

<class 'type'>學習

4.1type實現

  • 建立類的3個要素:類名,基類,類的名稱空間
  • People = type(類名,基類,類的名稱空間)
class_name = 'People'  # 類名

class_bases = (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(
    class_body,
    {},
    class_dic,
)
print(class_name)

Peoplecode

print(class_bases)

(<class 'object'>,)對象

print(class_dic)  # 類的名稱空間

{'country': 'China', '__init__': <function __init__ at 0x10a0bc048>, 'eat': <function eat at 0x10a0bcd08>}繼承

  • People = type(類名,基類,類的名稱空間)
People1 = type(class_name, class_bases, class_dic)
print(People1)

<class '__main__.People'>遞歸

obj1 = People1(1, 2)
obj1.eat()

1 is eatingip

  • class建立的類的調用
print(People)

<class '__main__.People'>內存

obj = People1(1, 2)
obj.eat()

1 is eating文檔

五丶自定義元類控制類的建立

  • 使用自定義的元類
class Mymeta(type):  # 只有繼承了type類才能稱之爲一個元類,不然就是一個普通的自定義類
    def __init__(self, class_name, class_bases, class_dic):
        print('self:', self)  # 如今是People
        print('class_name:', class_name)
        print('class_bases:', class_bases)
        print('class_dic:', class_dic)
        super(Mymeta, self).__init__(class_name, class_bases,
                                     class_dic)  # 重用父類type的功能

分析用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(類名,基類們,類的名稱空間)
    country = 'China'

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

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

self: <class '__main__.People'> class_name: People class_bases: (<class 'object'>,) class_dic: {'__module__': '__main__', '__qualname__': 'People', 'country': 'China', '__init__': <function People.__init__ at 0x10a0bcbf8>, 'eat': <function People.eat at 0x10a0bc2f0>}

5.1應用

  • 自定義元類控制類的產生過程,類的產生過程其實就是元類的調用過程
  • 咱們能夠控制類必須有文檔,可使用以下的方式實現
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('類名首字母必須大寫')
        super(Mymeta, self).__init__(class_name, class_bases,
                                     class_dic)  # 重用父類的功能
try:

    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)
except Exception as e:
    print(e)

類中必須有文檔註釋,而且文檔註釋不能爲空

六丶call

  • 要想讓obj這個對象變成一個可調用的對象,須要在該對象的類中定義一個方法、、__call__方法,該方法會在調用對象時自動觸發

    class Foo:
        def __call__(self, *args, **kwargs):
            print(args)
            print(kwargs)
            print('__call__實現了,實例化對象能夠加括號調用了')
    
    
    obj = Foo()
    obj('nash', age=18)

('nash',)
{'age': 18}
__call__實現了,實例化對象能夠加括號調用了

七丶new

咱們以前說類實例化第一個調用的是__init__,但__init__其實不是實例化一個類的時候第一個被調用 的方法。當使用 Persion(name, age) 這樣的表達式來實例化一個類時,最早被調用的方法 實際上是 new 方法。

__new__方法接受的參數雖然也是和__init__同樣,但__init__是在類實例建立以後調用,而 __new__方法正是建立這個類實例的方法。

注意:new() 函數只能用於從object繼承的新式類。建立類能夠理解爲init+new

class A:
    pass


class B(A):
    def __new__(cls):
        print("__new__方法被執行")
        return cls.__new__(cls)

    def __init__(self):
        print("__init__方法被執行")


b = B()

八丶自定義元類控制類的實例化

class Mymeta(type):
    def __call__(self, *args, **kwargs):
        print(self)  # self是People
        print(args)  # args = ('nick',)
        print(kwargs)  # kwargs = {'age':18}
        # return 123
        # 1. 先造出一個People的空對象,申請內存空間
        # __new__方法接受的參數雖然也是和__init__同樣,但__init__是在類實例建立以後調用,而 __new__方法正是建立這個類實例的方法。
        obj = self.__new__(self)  # 雖然和下面一樣是People,可是People沒有,找到的__new__是父類的
        # 2. 爲該對空對象初始化獨有的屬性
        self.__init__(obj, *args, **kwargs)
        # 3. 返回一個初始化好的對象
        return obj
  • People = Mymeta(),People()則會觸發__call__
class People(object, metaclass=Mymeta):
    country = 'China'

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

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


#     在調用Mymeta的__call__的時候,首先會找本身(以下函數)的,本身的沒有才會找父類的
#     def __new__(cls, *args, **kwargs):
#         # print(cls)  # cls是People
#         # cls.__new__(cls) # 錯誤,無限死循環,本身找本身的,會無限遞歸
#         obj = super(People, cls).__new__(cls)  # 使用父類的,則是去父類中找__new__
#         return obj
  • 類的調用,即類實例化就是元類的調用過程,能夠經過元類Mymeta的__call__方法控制

  • 分析:調用Pepole的目的

    1. 先造出一個People的空對象
    2. 爲該對空對象初始化獨有的屬性
    3. 返回一個初始化好的對象
    obj = People('nash', age=18)

<class '__main__.People'> ('nash',) {'age': 18}

print(obj.__dict__)

{'name': 'nash', 'age': 18}

九丶自定義元類後累的繼承順序

其實咱們用class自定義的類也全都是對象(包括object類自己也是元類type的 一個實例,能夠用type(object)查看),根據繼承的實現原理,若是把類當成對象去看,將下述繼承應該說成是:對象OldboyTeacher繼承對象Foo,對象Foo繼承對象Bar,對象Bar繼承對象object

class Mymeta(type):  # 只有繼承了type類才能稱之爲一個元類,不然就是一個普通的自定義類
    n = 444

    def __call__(self, *args,
                 **kwargs):  #self=<class '__main__.OldboyTeacher'>
        obj = self.__new__(self)
        self.__init__(obj, *args, **kwargs)
        return obj


class Bar(object):
    n = 333


class Foo(Bar):
    n = 222


class OldboyTeacher(Foo, metaclass=Mymeta):
    n = 111

    school = 'oldboy'

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

    def say(self):
        print('%s says welcome to the oldboy to learn Python' % self.name)


print(
    OldboyTeacher.n
)  # 自下而上依次註釋各個類中的n=xxx,而後從新運行程序,發現n的查找順序爲OldboyTeacher->Foo->Bar->object->Mymeta->type

111

print(OldboyTeacher.n)

111

  • 查找順序:
    1. 先對象層:OldoyTeacher->Foo->Bar->object
    2. 而後元類層:Mymeta->type

依據上述總結,咱們來分析下元類Mymeta中__call__裏的self.__new__的查找

class Mymeta(type):
    n = 444

    def __call__(self, *args,
                 **kwargs):  #self=<class '__main__.OldboyTeacher'>
        obj = self.__new__(self)
        print(self.__new__ is object.__new__)  #True


class Bar(object):
    n = 333

    # def __new__(cls, *args, **kwargs):
    #     print('Bar.__new__')


class Foo(Bar):
    n = 222

    # def __new__(cls, *args, **kwargs):
    #     print('Foo.__new__')


class OldboyTeacher(Foo, metaclass=Mymeta):
    n = 111

    school = 'oldboy'

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

    def say(self):
        print('%s says welcome to the oldboy to learn Python' % self.name)

    # def __new__(cls, *args, **kwargs):
    #     print('OldboyTeacher.__new__')


OldboyTeacher('nick',
              18)  # 觸發OldboyTeacher的類中的__call__方法的執行,進而執行self.__new__開始查找

總結,Mymeta下的__call__裏的self.__new__在OldboyTeacher、Foo、Bar裏都沒有找到__new__的狀況下,會去找object裏的__new__,而object下默認就有一個__new__,因此即使是以前的類均未實現__new__,也必定會在object中找到一個,根本不會、也根本不必再去找元類Mymeta->type中查找__new__

相關文章
相關標籤/搜索