[Python] Class 教學

程式語言:Python
官方文件

簡介:class 宣告與使用
  1. class ClassName:
  2. # name mangling "_ClassName__dis"
  3. __dis = 0
  4. _r = 10
  5. # 初始化
  6. def __init__(self, x, y):
  7. self.x = x
  8. self.y = y
  9.  
  10. c = ClassName(1, 2)
  11.  
  12. # 類別或實例本身會擁有一個__dict__特性參考至一個字典物件,其中記錄著類別或實例所擁有的特性
  13. # 如果實例的__dict__中沒有,則到產生實例的類別__dict__中尋找
  14. # 所以 c.__dict__ 看不到 _r
  15. print(c.__dict__)
  16. print(ClassName.__dict__)

宣告 attribute

如果 data attributes 和 method attributes 有相同名稱的話,會導致最先宣告的被覆蓋
要避免這個命名的衝突 (這常常是許多 bug 的由來)
建議的命名規則,如下
  • method 都是大寫的
  • data attribute 的前面加上一些小字串 (或者是底線)
  • method 都用動詞,data attribute 都用名詞

變數
宣告變數,在任意地方賦值即成立
  1. class ClassName:
  2. # private
  3. # 例:__spam 會被重命名為 _ClassName__spam
  4. # 最前面至少要有兩個底線,最後面最多只能有一個底線
  5. __private = 'private'
  6. __private_ = '123'
  7. __private_t = [1, 2, 3]
  8. # public
  9. __public__ = 10
  10. # 利用 __init__ 宣告變數
  11. def __init__(self, x):
  12. self.x = x
  13. # 呼叫其他變數
  14. self.__private = 'privateChange'
  15.  
  16. ClassName.y = 'y' # 在 class 等級加入變數
  17. ClassName.__y = 'y' # 無效,仍是 public
  18. c = ClassName('x')
  19. c.z = 'z' # 在 instance 等級加入變數,不影響 class 後續的宣告
  20. c.__z = 'y' # 無效,仍是 public
  21.  
  22. # 類別或實例本身會擁有一個__dict__特性參考至一個字典物件,其中記錄著類別或實例所擁有的特性
  23. # 如果實例的__dict__中沒有,則到產生實例的類別__dict__中尋找
  24. # 所以 c.__dict__ 看不到 _r
  25. print(c.__dict__)
  26. print(ClassName.__dict__)
  27.  
  28. # 可強制呼叫 private variable 甚至賦值,但這並不安全
  29. print(c._ClassName__private)

函式(function) & 方法(method)
宣告函式,在任意地方賦於即成立
  1. def beforeF(self):
  2. print('beforeF')
  3.  
  4. def afterF(self):
  5. print('afterF')
  6.  
  7. class ClassName:
  8. # 利用 __init__ 宣告變數
  9. def __init__(self, x):
  10. self.x = x
  11. # private
  12. # 例:__spam 會被重命名為 _ClassName__spam
  13. # 最前面至少要有兩個底線,最後面最多只能有一個底線
  14. __private = beforeF
  15. def __private_(self):
  16. print('__private_')
  17. # 習慣上,我們把一個method的第一個參數叫做 self 。這只是一個習慣
  18. # self 這個名字對 Python 來說完全沒有什麼特殊的意義
  19. # 但建議 follow,這可增加可讀性
  20. # self 改為 ttself 仍可正常執行
  21. def __public__(ttself):
  22. print('__public__')
  23. # 呼叫其他 function
  24. ttself.__private_()
  25.  
  26. ClassName.F = afterF
  27. c = ClassName('x')
  28. c.f = afterF # 在 instance 等級加入變數,不影響 class 後續的宣告
  29.  
  30. # 類別或實例本身會擁有一個__dict__特性參考至一個字典物件,其中記錄著類別或實例所擁有的特性
  31. # 如果實例的__dict__中沒有,則到產生實例的類別__dict__中尋找
  32. # 所以 c.__dict__ 看不到 _r
  33. print(c.__dict__)
  34. print(ClassName.__dict__)
  35.  
  36. c.__public__()
  37. # __public__
  38. # __private_
  39.  
  40. c.F()
  41. # afterF
  42.  
  43. # 可強制呼叫 private method 甚至重新賦於 function,但這並不安全
  44. c._ClassName__private()
  45. # beforeF
  46.  
  47. # TypeError: afterF() missing 1 required positional argument: 'self'
  48. # 主要是因為 instance method 是 class function 與 instance 的結合
  49. # 調用一個方法的時候,隱含 method 所處的 instance 會被當作 function 傳入的第一個參數
  50. c.f()

Function & Method 詳解

  • Function 即是在 class 中的函式
  • Method 即是在 instance 中的方法,是 function 與 instance 的結合
    調用時,instance 會被當作第一個參數傳遞過去

instancemethod
  1. class C:
  2. def f(self):
  3. print('f')
  4.  
  5.  
  6. print(C.f)
  7. # <function C.f at 0x0000000002FC7840>
  8. # 因未被綁定,所以需要提供 instance,才正常執行
  9. C.f(C())
  10.  
  11. c = C()
  12. print(c)
  13. print(c.f)
  14. # <__main__.C object at 0x0000000002FCDB38>
  15. # <bound method C.f of <__main__.C object at 0x0000000002FCDB38>>
  16. # 已被綁定,所以會自行將 method 的 instance 當作第一個參數傳入
  17. c.f()

classmethod
  1. class C:
  2. @classmethod
  3. def f(cls):
  4. print('f')
  5.  
  6.  
  7. print(C.f)
  8. # <bound method C.f of <class '__main__.C'>>
  9. # 被綁定在 class C 上,所以會自行將 class C 當作第一個參數傳入
  10. # 無需傳入參數
  11. C.f()
  12.  
  13. c = C()
  14. print(c)
  15. print(c.f)
  16. # <__main__.C object at 0x00000000029ADB70>
  17. # <bound method C.f of <class '__main__.C'>>
  18. # 被綁定在 class C 上,所以會自行將 class C 當作第一個參數傳入
  19. # 與原本的 instance 已無關
  20. c.f()

staticmethod
  1. class C:
  2. @staticmethod
  3. def f():
  4. print('f')
  5.  
  6.  
  7. print(C.f)
  8. # <function C.f at 0x0000000002FE7840>
  9. # 普通的 function
  10. # 無需傳入參數
  11. C.f()
  12.  
  13. c = C()
  14. print(c)
  15. print(c.f)
  16. # <__main__.C object at 0x0000000002FEDB70>
  17. # <function C.f at 0x0000000002FE7840>
  18. # 普通的 function
  19. # 與原本的 instance & class 已無關
  20. c.f()

繼承

支援多重繼承形式
  1. class ParentA:
  2. __private = 'private'
  3. def pF(self):
  4. print('parentA')
  5. def pFA(self):
  6. print('parentA part 2')
  7.  
  8. class ParentB:
  9. public = 'public'
  10. def pF(self):
  11. print('parentB')
  12. def pFB(self):
  13. print('parentB part 2')
  14.  
  15. class Child(ParentB, ParentA):
  16. def cF(self):
  17. print('child')
  18. # override
  19. def pFA(self):
  20. # 呼叫 ParentA 的 PFA
  21. super().pFA()
  22. # 呼叫 ParentB 的 PFB
  23. # 不建議使用此方法
  24. ParentB.pFB(self)
  25. print('child part 2')
  26. child = Child()
  27.  
  28. # 顯示可用變數 & method 含 private 變數
  29. print(dir(child))
  30.  
  31. child.cF()
  32. # child
  33.  
  34. # 同樣的名字時,規則是先深,而後由左至右(depth-first, left-to-right)
  35. child.pF()
  36. # parentB
  37.  
  38. child.pFA()
  39. # parentA part 2
  40. # parentB part 2
  41. # child part 2
  42.  
  43. print(child.public)
  44. # public
  45.  
  46. # AttributeError: 'Child' object has no attribute '__private'
  47. # 但有 _ParentA__private,可強制得到並賦值
  48. print(child._ParentA__private)
  49. print(child.__private)
  50.  

小技巧

@property
自定屬性
  1. class Ball:
  2. def __init__(self, radius):
  3. if radius <= 0:
  4. raise ValueError('必須是正數')
  5. self.__radius = radius
  6. @property
  7. def radius(self):
  8. return self.__radius
  9. @radius.setter
  10. def radius(self, radius):
  11. if radius <= 0:
  12. print('必須是正數')
  13. else:
  14. self.__radius = radius
  15. @radius.deleter
  16. def radius(self):
  17. del self.__radius
  18.  
  19. ball = Ball(10)
  20.  
  21. print(ball.radius)
  22. # 10
  23.  
  24. ball.radius = -15
  25. # 必須是正數
  26. print(ball.radius)
  27. # 10
  28.  
  29. ball.radius = 15
  30. print(ball.radius)
  31. # 15
  32.  
  33. del ball.radius
  34. print(ball.radius)
  35. # AttributeError: 'Ball' object has no attribute '_Ball__radius'

__call__
設定直接呼叫的方法
  1. class Ball:
  2. def __init__(self, radius):
  3. if radius <= 0:
  4. raise ValueError('必須是正數')
  5. self.radius = radius
  6. def __call__(self, radius):
  7. if radius <= 0:
  8. print('必須是正數')
  9. else:
  10. self.radius = radius
  11.  
  12. ball = Ball(10)
  13.  
  14. print(ball.radius)
  15. # 10
  16.  
  17. ball(15)
  18. print(ball.radius)
  19. # 15

__getitem__
__setitem__
__delitem__
__len__
自定 list, dic 等類似 object,numpy 便是以此建立 array 的
而 x[i] 等同於呼叫 type(x).__getitem__(x, i)

dict 範例
  1. class DictDemo:
  2. def __init__(self, key, value):
  3. self.dict = {}
  4. self.dict[key] = value
  5. def __getitem__(self, key):
  6. return self.dict.get(key)
  7. def __setitem__(self, key, value):
  8. self.dict[key] = value
  9. def __delitem__(self, key):
  10. del self.dict[key]
  11. def __len__(self):
  12. return len(self.dict)
  13. dictDemo = DictDemo('k0','v0')
  14. print(dictDemo['k0']) # v0
  15. dictDemo['k1'] = 'v1'
  16. print(dictDemo['k1']) # v1
  17. print(len(dictDemo)) # 2
  18. del dictDemo['k1']
  19. print(dictDemo['k1']) # None
  20. print(len(dictDemo)) # 1

二維 list 範例
  1. class ListDemo:
  2. def __init__(self, data):
  3. self.list = data
  4. def __getitem__(self, index):
  5. # index 會是 tuple
  6. return self.list[index[0]][index[1]]
  7. def __setitem__(self, index, value):
  8. self.list[index[0]][index[1]] = value
  9. def __delitem__(self, index):
  10. del self.list[index[0]][index[1]]
  11. def __len__(self):
  12. return len(self.list)
  13. listDemo = ListDemo([[1,2],
  14. [3,4]])
  15. print(listDemo[1,0]) # 3
  16. listDemo[1,1] = 'abc'
  17. print(listDemo[1,1]) # abc
  18. print(len(listDemo)) # 2
  19. del listDemo[1,1]
  20. print(listDemo[1,1]) # IndexError: list index out of range

參考

Python 教學文件
property() 函式 Special method names

留言