| >>> class Math: ... PI = 3.14159 ... >>> Math.PI 3.14159 >>> print(Math.__dict__) {'__dict__': <attribute '__dict__' of 'Math' objects>, '__module__': '__main__', 'PI': 3.14159, '__weakref__': <attribute '__weakref__' of 'Math' objects>, '__d oc__': None} >>> Math.__dict__['PI'] 3.14159 >>> |
在上例中,Math類別上定義了PI特性,這記錄在Math.__dict__中,你嘗試使用Math.PI,則使用Math.__dict__['PI']來尋找出對應的值。如果你試著透過實例來取得PI:
| >>> m = Math() >>> m.PI 3.14159 >>> print(m.__dict__) {} >>> |
實際上m所參考的實例,其__dict__中並沒有PI,此時會到Math.__dict__中找看看有無PI。這是Python中尋找特性的順序:如果實例的__dict__中沒有,則到產生實例的類別__dict__中尋找。如果你試著在m實例上設定PI特性:
| >>> m.PI 3.14 >>> Math.PI 3.14159 >>> print(m.__dict__) {'PI': 3.14} >>> print(Math.__dict__) {'__dict__': <attribute '__dict__' of 'Math' objects>, '__module__': '__main__', 'PI': 3.14159, '__weakref__': <attribute '__weakref__' of 'Math' objects>, '__d oc__': None} >>> |
實際上你並沒有改變Math.__dict__中的PI,而是在實例m.__dict__中新增一個PI,而你嘗試使用實例存取PI時,由於m.__dict__中已經有了,就直接取得該值。
這也說明了,為什麼實例方法的第一個參數會綁定至實例:
| >>> class Some: ... def setx(self, x): ... self.x = x ... >>> s = Some() >>> print(Some.__dict__) {'__dict__': <attribute '__dict__' of 'Some' objects>, '__weakref__': <attribute '__weakref__' of 'Some' objects>, '__module__': '__main__', 'setx': <function s etx at 0x018FA078>, '__doc__': None} >>> print(s.__dict__) {} >>> s.setx(10) >>> print(s.__dict__) {'x': 10} >>> |
類別中所定義的函式,其實就是類別的特性,也就是在類別的__dict__中可以找到該名稱。實例方法的第一個參數self綁定實例,透過self.x來設定特性值,也就是在self.__dict__中添增特性。
由於Python可以動態地為類別添加屬性,即使是未添加屬性前就已建立的物件,在類別動態添加屬性之後, 也可依Python的名稱空間搜尋順序套用上新的屬性,用這種方式,您可以為類別動態地添加方法。例如:
class Some:
def __init__(self, x):
self.x = x
s = Some(1)
Some.service = lambda self, y: print('do service...', self.x + y)
s.service(2) # do service... 3 如果你要刪除物件上的某個特性,則可以使用del。例如:
| >>> class Some: ... pass ... >>> s = Some() >>> s.x = 10 >>> print(s.__dict__) {'x': 10} >>> del s.x >>> print(s.__dict__) {} >>> |
如果你試著在實例上呼叫某個方法,而該實例上沒有該綁定方法時(被@staticmethod或@classmethod修飾的函式),則會試著去類別__dict__中尋找,並以類別呼叫方式來執行函式。例如:
| >>> class Some: ... @staticmethod ... def service(): ... print('XD') ... >>> s = Some() >>> s.service() XD >>> |
在上例中,嘗試執行s.service(),由於s並沒有service()的綁定方法(因為被@staticmethod修飾),所以嘗試尋找Some.service()執行。
實際上,如果嘗試透過實例取得某個特性,如果實例的__dict__中沒有,則到產 生實例的類別__dict__中尋找,如果類別__dict__仍沒有,則會試著呼叫__getattr__()來傳回,如果沒有定義 __getattr__()方法,則會引發AttributeError,如果有__getattr__(),則看__getattr__()如何處理。例如:
| >>> class Some: ... w = 10 ... def __getattr__(self, name): ... if name == 'w': ... return 20 ... >>> s = Some() >>> s.w 10 >>> s.x >>> class Some: ... def __getattr__(self, name): ... if name == 'w': ... return 20 ... else: ... raise AttributeError(name) ... >>> s = Some() >>> s.w 20 >>> s.x Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 6, in __getattr__ AttributeError: x >>> |
在類別中的函式執行過程中若有定義實例特性時,具特性名稱是以__開頭,則該名稱會被加工處理。例如:
| >>> class Some: ... def __init__(self): ... self.__x = 10 ... >>> s = Some() >>> s.__x Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Some' object has no attribute '__x' >>> print(s.__dict__) {'_Some__x': 10} >>> s._Some__x 10 >>> |
實例變數若以__name這樣的名稱,則會自動轉換為「_類別名__name」這樣的名稱儲存在實例的__dict__中,以__開頭的變數名稱,Python沒有真正阻止你存取它,但這提示不希望你直接存取。
如果不想要直接使用實例的__dict__來取得特性字典物件,則可以使用vars(),vars()會代為呼叫實例的__dict__。例如:
| >>> class Some: ... def __init__(self): ... self.x = 10 ... self.y = 20 ... >>> s = Some() >>> vars(s) {'y': 20, 'x': 10} >>> |
沒有留言:
張貼留言