Python中動態類和動態方法的建立與呼叫

小小程序员ol發表於2024-08-03

藉助於python的動態語言特性,很容易對物件進行新增方法或者屬性,這也是python的靈活之一。

動態生成類的屬性及其方法

在某些情況可能要根據不同的引數來動態生成不同的例項方法、靜態方法、類方法。下面的例子中則展示瞭如何動態地向類中新增屬性和方法。

import types

class Person():
	def __init__(self,name): 
		self.name = name 
    
li = Person('Lily') 
li.age = 20  # 例項屬性新增,僅對當下例項有效
tom = Person('Tom')
print(tom.age) # 'Person' object has no attribute 'age'

Person.age = None  # 類屬性新增 
print(tom.age)  # None

def eat(self): 
	print('%s正在吃東西。。'%self.name) 

li.eat = types.MethodType(eat, li) # 實際上python所有類都是type類的例項物件,動態新增了Person的例項方法
li.eat() 

@staticmethod  
def test():  
	print('這是一個靜態方法。')

Person.test = test  # 動態新增動態方法
Person.test()  
 
@classmethod  
def test(cls): 
    print(cls.age) # None,訪問動態建立的age
    print('這是一個類方法。')

Person.test = test # 動態新增類方法
Person.test() 
 
class test(object): 
	__slots__ = ('name','age') # 使用slots來將屬性固定,不允許增刪

動態地建立類

由於所有類都是type類的物件,所以也可以透過type動態地建立類。

Test = type('Test',(object,),{'num':0})  # 所有類都是type的物件,param1為類名,param2為繼承物件,num為類屬性,方法

class Test(object): # 與上述程式碼等效
    num = 0

如果需要新增屬性方法,則在相應的傳參字典中新增對應的方法,例如:

Test = type('Test',(object,),{'num':0, 'foo': fun})

動態訪問類中的屬性方法

動態地方法類中的屬性方法,也是一種反射機制。python中的反射/自省的實現,是透過hasattr、getattr、setattr、delattr四個內建函式實現的,其實這四個內建函式不只可以用在類和物件中,也可以用在模組等其他地方,只是在類和物件中用的很多,所以單獨提出來進行解釋。

  • hasattr(key) # 返回的是一個bool值,判斷某個成員或者屬性在不在類或者物件中
  • getattr(key,default=xxx) # 獲取類或者物件的成員或屬性,如果不存在,則會丟擲AttributeError異常,如果定義了default那麼當沒有屬性的時候會返回預設值。
  • setattr(key,value)假如有這個屬性,那麼更新這個屬性,如果沒有就新增這個屬性並賦值value
  • delattr(key)刪除某個屬性

其用法如下所示:

class Foo:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def show(self):
        return self.name,self.age

#學習中遇到問題沒人解答?小編建立了一個Python學習交流群:531509025
obj=Foo("Tom",18)
print(getattr(obj,"name"))  # Tom
setattr(obj,"k1",eat)
print(obj.k1) # <function eat at 0x00000162CAD661F0>
print(hasattr(obj,"k1")) # True
delattr(obj,"k1")
show_fun=getattr(obj,"show") 
print(show_fun()) # ('Tom', 18)

動態訪問普通全域性函式

有時候需要透過函式名來動態訪問全域性的函式,那麼依然有三種方法。

透過eval,不同由於考慮到安全因素,不能直接這樣去寫,可能會得到惡意程式碼

eval("print")("hello,world!")

透過建立字典,事先將需要呼叫的函式全部放入字典,缺點是每增加一個動態函式,就要更改字典:

dynamic_fun = {"print": print }
dynamic_fun["print"]("hello,world!")

透過呼叫global()來使用
global()維護了一個全域性的變數列表,其實現過程和方式2類似,具體使用時透過global().get(fun_name)來完成。

類中其他內建屬性方法(魔術方法)

__init__ # 構造初始化函式,__new__之後執行
__new__ # 建立例項所需的屬性,類似於構造方法
__call__ # 可以使得類的例項透過function方式訪問
__class__ # 例項所在的類,例項.__class__
__str__ # 例項的字串表示,可讀性高
__repr__ # 例項的字串表示,準確性高
__del__ # 刪除例項引用,類似於析構方法
__dict__ # 實力自定義屬性,vars(例項.__dict__)
__doc__ # 類文件,help(類或者例項)
__bases__ #當前類的所有父類
__getattribute__ #屬性訪問攔截器

相關文章