Python :: 屬性與方法

文章推薦指數: 80 %
投票人數:10人

類別方法 OPENHOME.CC Python |起步走 Hello,Python 簡介模組 IO/格式/編碼 |內建型態 數值 字串 清單 集合 字典 tuple |基本運算 變數 算術運算 比較、指定、邏輯運算 位元運算 |流程語法 if分支判斷 while迴圈、for迭代 forComprehension 初試match/case |函式入門 定義函式 一級函式、lambda運算式 初探變數範圍 yield產生器 |封裝 類別入門 屬性與方法 屬性名稱空間 特殊方法 callable物件 |繼承 共同行為與isa 使用enum列舉 多重繼承與Mixin |例外處理 使用try、except 例外繼承架構 raise例外 使用else、finally 使用withas 使用assert |模組/套件 管理模組名稱 模組路徑 使用套件 |metaprogramming __slots__、__abstractmethods__、__init_subclass__ __getattribute__、__getattr__、__setattr__、__delattr__ 裝飾器 描述器 type類別 metaclass super與mro GitHub Twitter Facebook LinkedIn 2DDesigns 3DDesigns Tags BuiltwithbyHugo HOME> Python> 封裝> 屬性與方法 定義內部屬性 定義外部屬性 綁定與未綁定方法 靜態方法 類別方法 encapsulation objectoriented uniformaccess namespace callable static 屬性與方法 April24,2022 在〈類別入門〉中,Account類別擁有name、number與balance三個屬性可供存取,雖然你設計了deposit、withdraw方法,希望使用者想變更Account物件的狀態時,都透過這些方法,然而,可能會有人如下誤用: acct=Account('Justin','123-4567',1000) acct.balance=1000000 定義內部屬性 如果想避免使用者這類誤用,可以使用self.__xxx的方式定義內部值域。

例如: classAccount: def__init__(self,name,number,balance): self.__name=name self.__number=number self.__balance=balance defdeposit(self,amount): ifamount<=0: print('存款金額不得為負') else: self.__balance+=amount defwithdraw(self,amount): ifamount>self.__balance: print('餘額不足') else: self.__balance-=amount def__str__(self): returnf"Account('{self.__name}','{self.__number}',{self.__balance})" 在Account類別的方法定義中,可以使用self.__name、self.__number、self.__balance來存取屬性;然而,若使用者建立Account實例並指定給acct,不能使用acct.__name、acct.__number、acct.__balance來進行屬性的存取(子類別中定義的方法也不能以self.__name、self.__number、self.__balance來存取屬性),這會引發AttributeError,因為屬性若使用__xxx這樣的名稱,會自動轉換為「_類別名稱__xxx」。

Python沒有完全阻止存取,只要在原本的屬性名稱前加上_類別名稱,仍舊可以存取到名稱為__開頭的屬性: acct=Account('Justin','123-4567',1000) print(acct._Account__name) acct._Account__balance=1 然而並不建議這麼做,__xxx名稱的屬性,慣例上是作為類別定義時,內部相關流程操作之用,外界最好不要知道其存在,更別說是操作了,如果真想這麼做,最好是清楚地知道自己在做些什麼。

兩個底線__開頭的屬性,基本上表示私有(private),只在定義的類別中使用;如果屬性想讓子類別存取,Python的慣例會使用一個底線_,代表它是一個受保護的(protected)屬性,這僅僅只是一個提示,python直譯器不會做任何變換,只是提示使用者存取該屬性必須慎重。

定義外部屬性 之前使用了__xxx這樣的格式來定義了內部屬性,不過留下了一個問題,若只是想取得帳戶名稱、帳號、餘額等資訊,以便在相關使用者介面上顯示,那該怎麼辦呢?以acct._Account__name這樣的方式是不建議的,那還能用什麼方式? 基本上,可以直接定義一些方法來傳回self.__name、self.__number這些內部屬性的值,例如: classAccount: 一些程式碼…略 defname(self): returnself.__name defnumber(self): returnself.__number defbalance(self): returnself.__balance 這麼一來,使用者就可以使用acct.name()、acct.number()這樣的方式來取得值,不過,針對這種情況,可以考慮在這類方法上加註@property,例如: classAccount: def__init__(self,name,number,balance): self.__name=name self.__number=number self.__balance=balance @property defname(self): returnself.__name @property defnumber(self): returnself.__number @property defbalance(self): returnself.__balance 其他程式碼同前一範例,故略… acct=Account('Justin','123-4567',1000) acct.deposit(500) acct.withdraw(200) print('帳戶名稱:',acct.name) print('帳戶號碼:',acct.number) print('帳戶餘額:',acct.balance) 現在使用者可以使用acct.name、acct.number、acct.balance這樣的形式取得值,然而,就目前的程式碼撰寫,無法直接使用acct.balance=10000這樣的形式來設定屬性值,因為@property只允許acct.balance這樣的形式取值。

如果在程式設計的一開始,沒有使用self.__balance的方式,而是以self.balance定義內部屬性,而使用者也使用了acct.balance取得值,後來進一步考慮要避免被誤用,想修改為self.__balance定義內部屬性,這時就可以像上面的範例,定義一個方法並加註@property,如此一來,使用者原本的程式碼也不會受到影響,這是固定存取原則(Uniformaccessprinciple)的實現。

如果這個範例,想進一步提供acct.balance=10000這樣的形式,可以使用@name.setter、@number.setter、@balance.setter標註對應的方法。

例如: classAccount: 略… @name.setter defname(self,name): #可實作一些設值時的條件控制 self.__name=name @number.setter defnumber(self,number): #可實作一些設值時的條件控制 self.__number=number @balance.setter defbalance(self,balance): #可實作一些設值時的條件控制 self.__balance=balance 略… 被@property標註的xxx取值方法(Getter),可以使用@xxx.setter標註對應的設值方法(Setter),使用@xxx.deleter來標註對應的刪除值之方法。

取值方法傳回值可以是即時運算的結果,而設值方法中,必要時可以使用流程語法等來實作一些存取控制。

綁定與未綁定方法 定義在類別中的方法,本質上也是函式,以目前定義的Account類別為例,執行type(Account.deposit)的話,會得到,如果建立了一個實例並指定給acct,呼叫acct.deposit(500)時,會將acct參考的實例傳給deposit的第一個self參數。

實際上,也可以如下取得相同效果: acct=Account('Justin','123-4567',1000) Account.deposit(acct,500) 不過,若試著將acct.deposit或acct.withdraw指定給一個變數,會發現變數實際上參考著一個綁定方法: >>>acct=Account('Justin','123-4567',1000) >>>deposit=acct.deposit >>>withdraw=acct.withdraw >>>deposit > >>>withdraw > >>>deposit(500) >>>withdraw(200) >>>print(acct) Account(Justin,123-4567,1300) >>> 綁定方法是method的實例(type(acct.deposit)會得到),行為上可以像函式進行呼叫,試著呈現acct.deposit或acct.withdraw的字串描述時,會出現'boundmethod'這樣的字樣,也就是說,此綁定方法綁定了一個Account實例,也就是方法的第一個參數self參考至Account實例。

在Python中,實作了__call__方法的物件,行為上可以像函式進行呼叫,稱為callable物件,其實到目前為止,你已經用過不少callable物件,例如,類別本身就是個callable物件。

使用acct.deposit(500)的方式來呼叫方法時,acct參考的物件,實際上就會傳給deposit方法的第一個參數,相對地,如果在類別中定義了一個方法,沒有任何參數會怎樣呢? >>>classSome: ...defnothing(): ...print('nothing') ... >>>s=Some() >>>s.nothing() Traceback(mostrecentcalllast): File"",line1,in TypeError:nothing()takes0positionalargumentsbut1wasgiven >>> 如果透過類別的實例呼叫方法時,點運算子左邊的物件會傳給方法作為第一個引數,然而,這邊的nothing方法沒有定義任何參數,因此發生了TypeError,並說明錯誤在於,試圖在呼叫時給予一個引數。

有沒有辦法取得綁定方法綁定的物件呢?雖然不鼓勵,不過確實可以透過綁定方法的__self__屬性來取得。

相對於綁定方法,像這樣定義在類別中,沒有定義self參數的方法,稱為未綁定方法(Unboundmethod),這類方法,充其量只是將類別名稱作為一種名稱空間,可以透過類別名稱來呼叫它,或取得函式物件進行呼叫: >>>Some.nothing() nothing >>>nothing=Some.nothing >>>nothing() nothing >>> 靜態方法 現在假設,想在Account類別增加一個default函式,以便建立預設帳戶,只需要指定名稱與帳號,開戶時餘額預設為100: classAccount: …略 defdefault(name,number): returnAccount(name,number,100) 當然,這個需求也可以在__init__上使用預設引數來達成,這邊只是為了示範,在更真實的情境中,可能是建構實例前,有個複雜的流程,像是收集、確認開戶者的其他資格等,因而有了default這類函式存在的需求。

你原本的用意,是希望default函式是以Account類別作為名稱空間,因為它與建立帳戶有關,而使用者應該要以Account.default('Monica','765-4321')這樣的方式來呼叫它,然而,若使用者如下誤用,正好也能夠執行: acct=Account('Justin','123-4567',1000) #顯示Account(Account(Justin,123-4567,1000),1000,100) print(acct.default(1000)) 就這個例子來說,acct參考的物件,傳給了default方法的第一個參數name,而執行過程正好也沒有引發錯誤,只不過顯示了怪異的結果。

若在定義類別時,希望某方法不被拿來作為綁定方法,可以使用@staticmethod加以標註。

例如: classAccount: …略 @staticmethod defdefault(name,number): returnAccount(name,number,100) 這麼一來,可以使用Account.default('Monica','765-4321')這樣的方式來呼叫它,就算使用者透過類別的實例來呼叫它,像是acct.default('Monica','765-4321'),acct也不會被傳入作為default的第一個參數。

雖然可以透過實例來呼叫@staticmethod標註的方法,但建議透過類別名稱來呼叫,明確地讓類別名稱作為靜態方法的名稱空間。

類別方法 來仔細看看上面的例子,default方法中寫死了Account這個名稱,萬一要修改類別名稱的話,還要記得修改default中的類別名稱,我們可以讓default的實作更有彈性。

首先得知道的是,在Python中定義的類別,也會產生對應的物件,這個物件會是type的實例。

例如: >>>classSome: ...pass ... >>>Some >>>type(Some) >>>s=Some() >>>s.__class__ >>>s.__class__() <__main__.someobjectat0x00000212ed2c9430> >>> 可以看到,也可以使用物件的__class__屬性來得知,該物件是從哪個類別建構而來,也可以透過取得的type實例來建構物件。

因此,只要能在先前的default方法中,取得目前所在類別的type實例,就可以不用寫死類別名稱了,對於這個需求,可以在default方法上標註@classmethod。

例如: classAccount: …略 @classmethod defdefault(cls,name:str,number:str): returncls(name,number,100) 類別中的方法若標註了@classmethod,第一個參數一定是接受所在類別的type實例,因此,在default方法中,就可以使用第一個參數來建構物件。



請為這篇文章評分?