Python常見面試題011. 如何在Python中動態建立類?

松勤吳老師發表於2023-03-08

011. 如何在Python中動態建立類?

說在前面

  • 答案是type

  • 你印象中的type是用來檢視物件的型別的

    li = []
    type(li)  # 得到list
    
  • 對自定義的類是這樣的

    class Person:
        pass
    
    wuxianfeng = Person()
    type(wuxianfeng)  # __main__.Person
    
  • 此處的wuxianfeng是一個Person類的例項

  • 既然一切皆物件,那麼Person的型別是啥呢?

    type(Person)  # type
    

去官網看看

https://docs.python.org/zh-cn/3.9/library/functions.html?highlight=type#type

class type(name, bases, dict, **kwds)

1. 傳入一個引數時,返回 object 的型別。 返回值是一個 type 物件,通常與 object.__class__ 所返回的物件相同。
2. 推薦使用 isinstance() 內建函式來檢測物件的型別,因為它會考慮子類的情況。

3. 傳入三個引數時,返回一個新的 type 物件。 這在本質上是 class 語句的一種動態形式,
	name 字串即類名並會成為 __name__ 屬性;
	bases 元組包含基類並會成為 __bases__ 屬性;如果為空則會新增所有類的終極基類 object。
    dict 字典包含類主體的屬性和方法定義;它在成為 __dict__ 屬性之前可能會被複製或包裝。 

4. 下面兩條語句會建立相同的 type 物件:
        class X:
            a = 1

        X = type('X', (), dict(a=1))

5. 提供給三引數形式的關鍵字引數會被傳遞給適當的元類機制 (通常為 __init_subclass__()),相當於類定義中關鍵字 (除了 metaclass) 的行為方式。

6. 在 3.6 版更改: type 的子類如果未過載 type.__new__,將不再能使用一個引數的形式來獲取物件的型別

稍作解釋

  1. 傳入一個引數時,返回 object 的型別。 返回值是一個 type 物件,通常與 object.class 所返回的物件相同
a = 1
a.__class__
type(a)  # 跟上面的結果是一樣的, 但不能1.__class__哦~
  1. isinstance
s1 = 'a'
isinstance(s1,str) # True
isinstance(s1,(str,int,list)) # 一樣也是True
class A:
    pass
class B(A):
    pass

b = B()
isinstance(b,B)  # True
isinstance(b,A) # 也是True

# 但是type不一樣,不會檢查繼承
type(b) == B # True
type(b) == A # False
  1. 上面是最簡單的使用,最關鍵的來了,type傳入三個引數時,返回一個新的 type 物件。 這在本質上是 class 語句的一種動態形式

    # 在Pycharm上檢視type的定義
    class type(object):
        """
        type(object_or_name, bases, dict)  # 2種用法
        type(object) -> the object's type # 上面講的用法1
        type(name, bases, dict) -> a new type # 建立動態類的另外一種方式
        """
    
    1. name 字串即類名並會成為 __name__ 屬性;# 字串型別
    2. bases 元組包含基類並會成為 __bases__ 屬性;如果為空則會新增所有類的終極基類 object。 
    3. dict 字典包含類主體的屬性和方法定義;它在成為 __dict__ 屬性之前可能會被複製或包裝。# 這裡可以填寫類屬性、類方式、靜態方法,採用字典格式,key為屬性名,value為屬性值
    

動態建立類

  • 官網案例

    class X:
        a = 1
    # 等價於
    X = type('X', (), dict(a=1))
    
  • 我們做點擴充

  • 示例1:一些典型的錯誤

    type('A',,)  # SyntaxError: invalid syntax
    
    type('A',(),)  # TypeError: type() takes 1 or 3 arguments
    
    type('A',(),1) # TypeError: type.__new__() argument 3 must be dict, not int
    
    
    
  • 示例2:類的建立

    # 依舊是錯誤的示範
    type('A',(),{}) # 相當於建立了一個類的名字叫A
    a = A()  # NameError: name 'A' is not defined  但你不能這麼用
    
    # 正確的
    B= type('B',(),{})
    b = B()
    type(b)  # __main__.B
    
    # 詭異的(不推薦)
    D = type('C',(),{})
    x = C()  # 哪個是對的?
    y = D()  # 哪個是對的?
    
    
    
    
    D = type('C',(),{})
    x = C()  # 提示報錯了,NameError: name 'C' is not defined
    
    y = D() # 對的
    type(y)  # 結果是啥?  __main__.C 你想到了嗎?當然也還是可以理解的
    # type('C',(),{}) 建立一個名叫C的類
    # 但D這個變數接收了上面的返回值,C是沒有這個變數的(不在記憶體中)
    
  • 示例3:帶繼承的類的建立

    class A:
        pass
    
    B = type('B',('A'),{})  # 這個錯比較明顯 
    # TypeError: type.__new__() argument 2 must be tuple, not str
    
    
    
    class A:
        pass
    
    B = type('B',('A',),{})
    
    TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
    # 其實錯在你不能用'A',這是個str,實際上此處應該是一個類名
    
    • 應該這樣
    class A:
        pass
    
    B = type('B',(A,),{})
    b = B() # 例項化沒問題
    print(B.__mro__) # mro也是ok的
    # (<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
    
    

  • 示例4:帶類屬性的建立

    C = type('C',(),{'name':'wuxianfeng'})
    print(C.name)  # 輸出 wuxianfeng
    
    • 注意是類屬性,並不是例項屬性

  • 示例5:帶例項方法的建立

    def say(self):
        self.name = 'wuxianfeng'
        print(f'calling method say, my name is {self.name}')
    Person = type('Person',(),{'say':say})
    wxf = Person()
    wxf.say()   # calling method say, my name is wuxianfeng
    
    
  • 多一個例子(因為這是最常見的做法)

    def run(self):
        return 'calling run'
    
    Foo = type('Foo',
               (object,),
               {'value':1,
                'func':lambda self:'calling func',
               'run':run})
    foo = Foo()
    print(foo.value) # 輸出 1
    result = foo.func() # 接收返回值‘calling func’
    print(result)   # 輸出 calling func
    print(foo.run()) # 輸出 calling run
    

說在最後

  • class建立類的本質就是用type建立,所以可以說python中所有類都是type建立的,包括整數、字串、函式以及使用者自定義的類

  • 當type()只有一個引數時,其作用就是返回變數或物件的型別

  • 當type()有三個引數時,其作用就是建立類物件

  • 透過type新增的屬性是類屬性,不是例項屬性

  • type就是Python的內建元類

相關文章