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__,將不再能使用一個引數的形式來獲取物件的型別
稍作解釋
- 傳入一個引數時,返回 object 的型別。 返回值是一個 type 物件,通常與 object.class 所返回的物件相同
a = 1
a.__class__
type(a) # 跟上面的結果是一樣的, 但不能1.__class__哦~
- 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
-
上面是最簡單的使用,最關鍵的來了,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的內建元類