[Python] Class 教學

程式語言:Python
官方文件

簡介:class 宣告與使用
class ClassName:
    # name mangling "_ClassName__dis"
    __dis = 0
    _r = 10
    
    # 初始化
    def __init__(self, x, y):
        self.x = x
        self.y = y
    

c = ClassName(1, 2)

# 類別或實例本身會擁有一個__dict__特性參考至一個字典物件,其中記錄著類別或實例所擁有的特性
# 如果實例的__dict__中沒有,則到產生實例的類別__dict__中尋找
# 所以 c.__dict__ 看不到 _r
print(c.__dict__)
print(ClassName.__dict__)

宣告 attribute

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

變數
宣告變數,在任意地方賦值即成立
class ClassName:
    # private
    # 例:__spam 會被重命名為 _ClassName__spam
    # 最前面至少要有兩個底線,最後面最多只能有一個底線
    __private = 'private'
    __private_ = '123'
    __private_t = [1, 2, 3]
    # public
    __public__ = 10
    
    # 利用 __init__ 宣告變數
    def __init__(self, x):
        self.x = x
        # 呼叫其他變數
        self.__private = 'privateChange'

ClassName.y = 'y' # 在 class 等級加入變數
ClassName.__y = 'y' # 無效,仍是 public
c = ClassName('x')
c.z = 'z'  # 在 instance 等級加入變數,不影響 class 後續的宣告
c.__z = 'y' # 無效,仍是 public

# 類別或實例本身會擁有一個__dict__特性參考至一個字典物件,其中記錄著類別或實例所擁有的特性
# 如果實例的__dict__中沒有,則到產生實例的類別__dict__中尋找
# 所以 c.__dict__ 看不到 _r
print(c.__dict__)
print(ClassName.__dict__)

# 可強制呼叫 private variable 甚至賦值,但這並不安全
print(c._ClassName__private)

函式(function) & 方法(method)
宣告函式,在任意地方賦於即成立
def beforeF(self):
    print('beforeF')

def afterF(self):
    print('afterF')

class ClassName:
    # 利用 __init__ 宣告變數
    def __init__(self, x):
        self.x = x
    
    # private
    # 例:__spam 會被重命名為 _ClassName__spam
    # 最前面至少要有兩個底線,最後面最多只能有一個底線
    __private = beforeF
    def __private_(self):
        print('__private_')
    
    # 習慣上,我們把一個method的第一個參數叫做 self 。這只是一個習慣
    # self 這個名字對 Python 來說完全沒有什麼特殊的意義
    # 但建議 follow,這可增加可讀性
    # self 改為 ttself 仍可正常執行
    def __public__(ttself):
        print('__public__')
        # 呼叫其他 function
        ttself.__private_()
    

ClassName.F = afterF
c = ClassName('x')
c.f = afterF  # 在 instance 等級加入變數,不影響 class 後續的宣告

# 類別或實例本身會擁有一個__dict__特性參考至一個字典物件,其中記錄著類別或實例所擁有的特性
# 如果實例的__dict__中沒有,則到產生實例的類別__dict__中尋找
# 所以 c.__dict__ 看不到 _r
print(c.__dict__)
print(ClassName.__dict__)

c.__public__()
# __public__
# __private_

c.F()
# afterF

# 可強制呼叫 private method 甚至重新賦於 function,但這並不安全
c._ClassName__private()
# beforeF

# TypeError: afterF() missing 1 required positional argument: 'self'
# 主要是因為 instance method 是 class function 與 instance 的結合
# 調用一個方法的時候,隱含 method 所處的 instance 會被當作 function 傳入的第一個參數
c.f()

Function & Method 詳解

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

instancemethod
class C:
    def f(self):
        print('f')


print(C.f)
# <function C.f at 0x0000000002FC7840>
# 因未被綁定,所以需要提供 instance,才正常執行
C.f(C())

c = C()
print(c)
print(c.f)
# <__main__.C object at 0x0000000002FCDB38>
# <bound method C.f of <__main__.C object at 0x0000000002FCDB38>>
# 已被綁定,所以會自行將 method 的 instance 當作第一個參數傳入
c.f()

classmethod
class C:
    @classmethod
    def f(cls):
        print('f')


print(C.f)
# <bound method C.f of <class '__main__.C'>>
# 被綁定在 class C 上,所以會自行將 class C 當作第一個參數傳入
# 無需傳入參數
C.f()

c = C()
print(c)
print(c.f)
# <__main__.C object at 0x00000000029ADB70>
# <bound method C.f of <class '__main__.C'>>
# 被綁定在 class C 上,所以會自行將 class C 當作第一個參數傳入
# 與原本的 instance 已無關
c.f()

staticmethod
class C:
    @staticmethod
    def f():
        print('f')


print(C.f)
# <function C.f at 0x0000000002FE7840>
# 普通的 function
# 無需傳入參數
C.f()

c = C()
print(c)
print(c.f)
# <__main__.C object at 0x0000000002FEDB70>
# <function C.f at 0x0000000002FE7840>
# 普通的 function
# 與原本的 instance & class 已無關
c.f()

繼承

支援多重繼承形式
class ParentA:
    __private = 'private'
    def pF(self):
        print('parentA')
    
    def pFA(self):
        print('parentA part 2')

class ParentB:
    public = 'public'
    def pF(self):
        print('parentB')
        
    def pFB(self):
        print('parentB part 2')
        

class Child(ParentB, ParentA):
    def cF(self):
        print('child')
    
    # override
    def pFA(self):
        # 呼叫 ParentA 的 PFA
        super().pFA()
        
        # 呼叫 ParentB 的 PFB
        # 不建議使用此方法
        ParentB.pFB(self)
        
        print('child part 2')
        
child = Child()

# 顯示可用變數 & method 含 private 變數
print(dir(child))

child.cF()
# child

# 同樣的名字時,規則是先深,而後由左至右(depth-first, left-to-right)
child.pF()
# parentB

child.pFA()
# parentA part 2
# parentB part 2
# child part 2

print(child.public)
# public

# AttributeError: 'Child' object has no attribute '__private'
# 但有 _ParentA__private,可強制得到並賦值
print(child._ParentA__private)
print(child.__private)


小技巧

@property
自定屬性
class Ball:
    def __init__(self, radius):
        if radius <= 0:
            raise ValueError('必須是正數')
        self.__radius = radius
    
    @property
    def radius(self):
        return self.__radius
        
    @radius.setter
    def radius(self, radius):
        if radius <= 0:
            print('必須是正數')
        else:
            self.__radius = radius
    
    @radius.deleter
    def radius(self):
        del self.__radius
        

ball = Ball(10)

print(ball.radius)
# 10

ball.radius = -15
# 必須是正數
print(ball.radius)
# 10

ball.radius = 15
print(ball.radius)
# 15

del ball.radius
print(ball.radius)
# AttributeError: 'Ball' object has no attribute '_Ball__radius'

__call__
設定直接呼叫的方法
class Ball:
    def __init__(self, radius):
        if radius <= 0:
            raise ValueError('必須是正數')
        self.radius = radius
        
    def __call__(self, radius):
        if radius <= 0:
            print('必須是正數')
        else:
            self.radius = radius
        

ball = Ball(10)

print(ball.radius)
# 10

ball(15)
print(ball.radius)
# 15

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

dict 範例
class DictDemo:  
    def __init__(self, key, value):  
        self.dict = {}
        self.dict[key] = value  
    def __getitem__(self, key):  
        return self.dict.get(key)  
    def __setitem__(self, key, value):  
        self.dict[key] = value
    def __delitem__(self, key):
        del self.dict[key]
    def __len__(self):  
        return len(self.dict) 
dictDemo = DictDemo('k0','v0')  
print(dictDemo['k0']) # v0
dictDemo['k1'] = 'v1'  
print(dictDemo['k1']) # v1  
print(len(dictDemo)) # 2  
del dictDemo['k1']
print(dictDemo['k1']) # None
print(len(dictDemo)) # 1

二維 list 範例
class ListDemo:  
    def __init__(self, data):  
        self.list = data
    def __getitem__(self, index):
        # index 會是 tuple
        return self.list[index[0]][index[1]]
    def __setitem__(self, index, value):  
        self.list[index[0]][index[1]]  = value
    def __delitem__(self, index):
        del self.list[index[0]][index[1]]
    def __len__(self):  
        return len(self.list) 
listDemo = ListDemo([[1,2],
                     [3,4]])  
print(listDemo[1,0]) # 3
listDemo[1,1] = 'abc'  
print(listDemo[1,1]) # abc
print(len(listDemo)) # 2  
del listDemo[1,1]
print(listDemo[1,1]) # IndexError: list index out of range

參考

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

留言