溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Python進階版定義類時應用的最佳做法有哪些

發(fā)布時間:2021-10-28 13:47:45 來源:億速云 閱讀:104 作者:iii 欄目:編程語言

本篇內(nèi)容主要講解“Python進階版定義類時應用的最佳做法有哪些”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Python進階版定義類時應用的最佳做法有哪些”吧!

作為一種OOP語言,Python通過支持以對象為主的各種功能來處理數(shù)據(jù)和功能。例如,數(shù)據(jù)結構是所有對象,包括原始類型(例如整數(shù)和字符串),而在某些其他語言中,原始類型則不視為對象。對于另一個實例,函數(shù)是所有對象,它們僅僅是定義了其他對象的屬性(例如類或模塊)。

盡管可以使用內(nèi)置數(shù)據(jù)類型,而且無需創(chuàng)建任何自定義類就能編寫一組函數(shù),但隨著項目范圍的擴大,代碼可能會越來越難維護。這些單獨代碼部分的主題并不相同,盡管有很多信息是相關的,但管理它們之間的聯(lián)系卻并不簡單。

在這些情況下,定義自己的類就很劃得來了,這樣一來你可以對相關信息進行分組并且改善項目的結構設計。而且由于你即將處理更少的分段代碼,代碼庫的長期可維護性將得到改善。但要注意,僅當以正確方式完成類聲明時,操作才可以實現(xiàn),定義自定義類的益處才能超過管理它們的支出。

1. 好的命名

定義自己的類,就好比在代碼庫中添加了一位新成員。因此應該給類起個好名字。雖然類名的唯一限制是合法Python變量的規(guī)則(例如,不能以數(shù)字開頭),但是有一些好用的方法來命名類。

  • 使用易于發(fā)音的名詞。在參與團隊項目時,這一點尤其重要。在小組演講中,你恐怕不愿意這樣講:“在這種情況下,我們創(chuàng)建Zgnehst類的實例?!? 另外,易于發(fā)音也意味著名稱不應太長,使用三個以上的單詞來定義類名簡直無法想象。一個字是最佳,兩個字其次,三個字不能再多啦!

  • 反映其存儲的數(shù)據(jù)和預期功能。就像在現(xiàn)實生活中一樣——當看到男性化的名字時,我們就會默認這個孩子是男孩。同樣的方式也適用于類名(或通常的任何其他變量),命名規(guī)則很簡單——不要讓人感覺奇怪。如果要處理學生的信息,那么該課程應該命名為Student,KiddosAtCampus并不是一個常規(guī)的好名字。

  • 遵循命名約定。應該對類名使用駱駝拼寫法,例如GoodName。以下是非常規(guī)類名稱的不完整列表:goodName,Good_Name,good_name以及GOodnAme。遵循命名約定是為了使意圖表現(xiàn)明確。在別人閱讀你的代碼時,可以毫無疑問地假定命名為GoodName的對象是一個類。

也有適用于屬性和功能的命名規(guī)則和約定,以下各節(jié)將在使用情況下簡要提及,但是總體原理是相同的。

2. 顯式實例屬性

在大多數(shù)情況下,我們都想定義自己的實例初始化方法(即__init__)。在此種方法中,設置了新創(chuàng)建的類實例的初始狀態(tài)。但是,Python并沒有限制可以在何處使用自定義類定義實例屬性。換句話說,你可以在創(chuàng)建實例之后的后續(xù)操作中定義其他實例屬性。

classStudent:            def__init__(self, first_name, last_name):                self.first_name = first_name                self.last_name = last_name                  defverify_registration_status(self):                status = self.get_status()                self.status_verified = status =="registered"                  defget_guardian_name(self):                self.guardian ="Goodman"                  defget_status(self):                # get the registration status from a database                status =query_database(self.first_name, self.last_name)                return status

(1) 初始化方法

如上所示,可以通過指定學生的名字和姓氏來創(chuàng)建“學生”類的實例。稍后,在調(diào)用實例方法(即verify_registration_status)時,將設置“學生實例”的status屬性。

但這不是理想的模式,因為如果在整個類中散布了各種實例屬性,那么該類就無法明確實例對象擁有哪些數(shù)據(jù)。因此,最佳做法是將實例的屬性放在__init__方法中,這樣代碼閱讀器就可以通過單一位置來了解你的類的數(shù)據(jù)結構,如下所示:

classStudent:            def__init__(self, first_name, last_name):                self.first_name = first_name                self.last_name = last_name                self.status_verified =None                self.guardian =None

(2) 更好的初始化方法

對于最初無法設置的那些實例屬性的問題,可以使用占位符值(例如None)進行設置。盡管沒什么好擔心的,但是當忘記調(diào)用某些實例方法來設置適用的實例屬性時,此更改還有助于防止可能的錯誤,從而導致AttributeError(‘Student’  object has noattribute ‘status_verified’)。

在命名規(guī)則方面,應使用小寫字母命名屬性,并遵循蛇形命名法——如果使用多個單詞,請在它們之間使用下劃線連接。此外,所有名稱都應對其存儲的數(shù)據(jù)有具有意義的指示(例如first_name比fn更好)。

3. 使用屬性;但要精簡

有些人在具備其他OOP語言(例如Java)背景的情況下學習Python編碼,并且習慣于為實例的屬性創(chuàng)建getter和setter??梢酝ㄟ^在Python中使用屬性裝飾器來模仿這一模式。以下代碼展示了使用屬性裝飾器實現(xiàn)getter和setter的基本形式:

classStudent:            def__init__(self, first_name, last_name):                self.first_name = first_name                self.last_name = last_name                @property           defname(self):                print("Getter for the name")                returnf"{self.first_name}{self.last_name}"                @name.setter           defname(self, name):                print("Setter for the name")                self.first_name, self.last_name = name.split()

(3) 屬性裝飾

創(chuàng)建此屬性后,盡管它是通過內(nèi)部函數(shù)實現(xiàn)的,我們?nèi)匀豢梢允褂命c符號將其用作常規(guī)屬性。

>>> student =Student("John", "Smith")                 ... print("StudentName:", student.name)                 ... student.name ="JohnnySmith"                 ... print("Aftersetting:", student.name)                 ...                Getterfor the name                StudentName: JohnSmith                Setterfor the name                Getterfor the name

(4) 使用屬性

使用屬性實現(xiàn)的優(yōu)點包括驗證正確的值設置(檢查是否使用字符串,而不是使用整數(shù))和只讀訪問權限(通過不實現(xiàn)setter方法)。但應該同時使用屬性,如果自定義類如下所示,可能會很讓人分心——屬性太多了!

classStudent:            def__init__(self, first_name, last_name):                self._first_name = first_name                self._last_name = last_name                @property            deffirst_name(self):                return self._first_name                @property            deflast_name(self):                return self._last_name                @property            defname(self):                returnf"{self._first_name}{self._last_name}"

(5) 濫用屬性

在大多數(shù)情況下,這些屬性可以用實例屬性代替,因此我們可以訪問它們并直接設置它們。除非對使用上述屬性的好處有特定的需求(例如:值驗證),否則使用屬性優(yōu)先于在Python中創(chuàng)建屬性。

4. 定義有意義的字符串表示法

在Python中,名稱前后帶有雙下劃線的函數(shù)稱為特殊方法或魔術方法,有些人將其稱為dunder方法。這些方法對解釋器的基本操作有特殊的用法,包括我們先前介紹的__init__方法。__repr__和__str__這兩種特殊方法對于創(chuàng)建自定義類的正確字符串表示法至關重要,這將為代碼閱讀器提供有關類的更直觀信息。

它們之間的主要區(qū)別在于__repr__方法定義了字符串,你可以使用該字符串通過調(diào)用eval(repr(“therepr”))重新創(chuàng)建對象,而__str__方法定義的字符串則更具描述性,并允許更多定制。換句話說,你可以認為__repr__方法中定義的字符串由開發(fā)人員查看,而__str__方法中使用的字符串由常規(guī)用戶查看。請看以下示例:

classStudent:            def__init__(self, first_name, last_name):               self.first_name = first_name               self.last_name = last_name                 def__repr__(self):               returnf"Student({self.first_name!r}, {self.last_name!r})"                  def__str__(self):               returnf"Student: {self.first_name}{self.last_name}"

字符串表示法的實現(xiàn):

請注意,在__repr__方法的實現(xiàn)中,f字符串使用!r來顯示帶引號的這些字符串,因為使用格式正確的字符串構造實例很有必要。如果不使用!r格式,則字符串將為Student(John,  Smith),這不是構造“學生”實例的正確方法。

來看看這些實現(xiàn)如何為我們顯示字符串:在交互式解釋器中訪問對象時會調(diào)用__repr__方法,而在打印對象時默認會調(diào)用__str__方法。

>>> student =Student("David", "Johnson")                  >>> student                 Student('David', 'Johnson')                  >>>print(student)                 Student: DavidJohnson

字符串表示法

5. 實例方法,類方法和靜態(tài)方法

在一個類中,我們可以定義三種方法:實例方法、類方法和靜態(tài)方法。我們需要考慮針對所關注的功能應使用哪些方法,以下是一些常規(guī)準則。

例如,如果方法與單個實例對象有關,那么需要訪問或更新實例的特定屬性。在這種情況下,應使用實例方法。這些方法具有如下簽名:def  do_something(self):,其中self自變量引用調(diào)用該方法的實例對象。

如果方法與單個實例對象無關,則應考慮使用類方法或靜態(tài)方法??梢允褂眠m用的修飾符輕松定義這兩種方法:類方法(classmethod)和靜態(tài)方法(staticmethod)。

兩者之間的區(qū)別在于,類方法允許你訪問或更新與類相關的屬性,而靜態(tài)方法則獨立于任何實例或類本身。類方法的一個常見示例是提供一種方便的實例化方法,而靜態(tài)方法可以只是一個實用函數(shù)。請看以下代碼示例:

classStudent:     def__init__(self,first_name, last_name):        self.first_name = first_name        self.last_name = last_name                defbegin_study(self):         print(f"{self.first_name}{self.last_name} beginsstudying.")                @classmethod      deffrom_dict(cls,name_info):         first_name = name_info['first_name']        last_name = name_info['last_name']         returncls(first_name,last_name)             @staticmethod      defshow_duties():          return"Study,Play, Sleep"

不同的方法

也可以用類似的方式創(chuàng)建類屬性。與前面討論的實例屬性不同,類屬性由所有實例對象共享,并且它們應當反映一些獨立于各個實例對象的特征。

6. 使用私有屬性進行封裝

在為項目編寫自定義類時,需要考慮封裝問題,尤其期望其他人也使用你的類的話就更應如此。當類的功能增長時,某些功能或屬性僅與類內(nèi)數(shù)據(jù)處理相關。換句話說,除了類之外,這些函數(shù)都將不會被調(diào)用,并且除你之外其他使用類的用戶甚至不會在意這些函數(shù)的實現(xiàn)細節(jié)。在這些情況下,應該考慮封裝。

按照慣例,應用封裝的一種重要方法是為屬性和函數(shù)加上下劃線或兩個下劃線。二者之間有著細微的區(qū)別:帶有下劃線的被認為是受保護的,而帶有兩個下劃線的被認為是私有的,這涉及在創(chuàng)建后進行名稱處理。

從本質(zhì)上來說,像這樣命名屬性和功能,是在告訴IDE(即集成開發(fā)環(huán)境,例如PyCharm),盡管在Python中不存在真正的私有屬性,但它們不會在類之外被訪問。

classStudent:     def__init__(self,first_name, last_name):        self.first_name = first_name        self.last_name = last_name                defbegin_study(self):         print(f"{self.first_name}{self.last_name} beginsstudying.")                @classmethod      deffrom_dict(cls,name_info):         first_name = name_info['first_name']        last_name = name_info['last_name']         returncls(first_name,last_name)             @staticmethod      defshow_duties():          return"Study,Play, Sleep"

封裝

上面的代碼展示了一個簡單的封裝示例。如果想了解學生的評價GPA,那么我們可以使用get_mean_gpa方法獲得GPA。用戶不需要知道平均GPA的計算方式,我們可以通過在函數(shù)名稱前添加下劃線來保護相關方法。

這一最佳做法的主要收獲是,與用戶使用你的代碼相關的公共API,僅公開最少的數(shù)量。對于僅在內(nèi)部使用的那些代碼,請將其設置為受保護的方法或私有方法。

7. 分離關注點和解耦

隨著項目的發(fā)展,你會發(fā)現(xiàn)自己正在處理更多數(shù)據(jù),如果你只堅持使用一個類會變得很麻煩。繼續(xù)以“學生”類為例,假設學生在學校吃午餐,并且每個人都有一個餐飲帳戶,可以用來支付餐費。從理論上講,我們可以處理學生類中與帳戶相關的數(shù)據(jù)和功能,如下所示:

classStudent:            def__init__(self, first_name, last_name, student_id):                self.first_name = first_name                self.last_name = last_name                self.student_id = student_id                  defcheck_account_balance(self):                account_number =get_account_number(self.student_id)                balance =get_balance(account_number)               return balance                  defload_money(self, amount):                account_number =get_account_number(self.student_id)                balance =get_balance(account_number)               balance += amount               update_balance(account_number, balance)

混合功能

上面的代碼向展示了一些有關檢查賬戶余額和向賬戶充值的偽代碼,這兩種偽代碼都在Student類中實現(xiàn)。還有更多與該帳戶相關的操作,例如凍結丟失的卡、合并帳戶——實施所有這些操作會使“學生”類越來越大,從而使維護變得越來越困難。你應該分離這些職責并使學生類不負責這些與帳戶相關的功能,即一種稱為解耦的設計模式。

classStudent:            def__init__(self, first_name, last_name, student_id):                self.first_name = first_name                self.last_name = last_name                self.student_id = student_id                self.account =Account(self.student_id)                  defcheck_account_balance(self):                return self.account.get_balance()                  defload_money(self, amount):                self.account.load_money(amount)              classAccount:           def__init__(self, student_id):                self.student_id = student_id                # get additional information from the database                self.balance =400                  defget_balance(self):                # Theoretically, student.account.balance will work, but just in case                # we need to have additional steps to check, such as query the database                # again to make sure the data is up to date                return self.balance                  defload_money(self, amount):                # get the balance from the database                self.balance += amount                self.save_to_database()

分離關注點

上面的代碼展示了我們?nèi)绾问褂酶郊拥腁ccount類來設計數(shù)據(jù)結構。如你所見,我們將所有與帳戶相關的操作移至Account類。要實現(xiàn)檢索學生的帳戶信息的功能,學生類將通過從Account類中檢索信息來處理。如果想實現(xiàn)更多與該類相關的功能,只需簡單地更新Account類即可。

設計模式的主要要點是,希望各個類具有單獨的關注點。通過將這些職責分開,你的類將變小,處理較小的代碼組件會使將來的更改變得更容易。

8. 考慮使用__slots__進行優(yōu)化

如果你的類主要用于存儲數(shù)據(jù)的數(shù)據(jù)容器,那么可以考慮使用__slots__來優(yōu)化類的性能。它不僅可以提高屬性訪問的速度,還可以節(jié)省內(nèi)存,如果需要創(chuàng)建數(shù)千個或更多實例對象,就是它發(fā)揮大作用之處啦。

原因是,對于常規(guī)類,實例屬性是通過內(nèi)部托管的字典存儲的。相比之下,通過使用__slots__,實例屬性將使用在幕后使用C語言實現(xiàn)的與數(shù)組相關的數(shù)據(jù)結構存儲,并且以更高的效率優(yōu)化了它們的性能。

classStudentRegular:                     def__init__(self,first_name, last_name):                         self.first_name = first_name                         self.last_name = last_name              classStudentSlot:                    __slots__ = ['first_name', 'last_name']                  def__init__(self,first_name, last_name):                         self.first_name = first_name                         self.last_name = last_name

在類的定義中使用__slots__

上面的代碼展示了如何在類中實現(xiàn)__slots__的簡單示例。具體來說,將所有屬性列為一個序列,這將在數(shù)據(jù)存儲中創(chuàng)建一對一匹配,以加快訪問速度并減少內(nèi)存消耗。如前所述,常規(guī)類使用字典進行屬性訪問,但不使用已實現(xiàn)__slots__的字典。以下代碼證實了這一點:

 
>>> student_r =StudentRegular('John', 'Smith')                        >>>student_r.__dict__                        {'first_name': 'John', 'last_name': 'Smith'}                        >>> student_s =StudentSlot('John', 'Smith')                        >>>student_s.__dict__                        Traceback (most recentcall last):                         File"<input>", line 1, in <module>                        AttributeError: 'StudentSlot' object has noattribute '__dict__'

具有__slots__的類中沒有__dict__

有關使用__slots__的詳細討論可以在Stack  Overflow找到答案,你也可以從官方文檔中找到更多信息(https://docs.python.org/3/reference/datamodel.html)。

需要注意,使用__slots__會有一個副作用&mdash;&mdash;它會阻止你動態(tài)創(chuàng)建其他屬性。有人建議將其作為一種控制類擁有的屬性的機制,但這并不是它的設計初衷。

9. 文件

最后我們必須討論一下類的文檔。我們需要明白編寫文檔并不能替代任何代碼,編寫大量文檔并不能提高代碼的性能,也不一定會使代碼更具可讀性。如果必須依靠文檔字符串來澄清代碼,那么你的代碼很可能有問題。

以下代碼將向大家展示一個程序員可能犯的錯誤&mdash;&mdash;使用不必要的注釋來補償錯誤的代碼(即,在這種情況下,無意義的變量名)。相比之下,一些有好名字的好代碼甚至不需要注釋。

# how many billable hours         a =6         # the hourly rate         b =100         # total charge         c = a * b              # The above vs.the below with no comments              billable_hours =6         hourly_rate =100         total_charge = billable_hours * hourly_rate

失敗解釋案例

我并不是說反對寫評論和文檔字符串,這實際上取決于自己的實例。如果你的代碼被多個人使用或多次使用(例如,你是唯一一個多次訪問同一代碼的人),那么就應考慮編寫一些好的注釋。

這些注釋可以幫助你自己或者團隊伙伴閱讀你的代碼,但是他們都不可以假定你的代碼完全按照注釋中的說明進行。換句話說,編寫好的代碼始終是需要牢記的頭等大事。

如果最終用戶要使用代碼的特定部分,那么需要編寫文檔字符串,因為這些人對相關的代碼庫并不熟悉。他們只想知道如何使用相關的API,而文檔字符串將構成幫助菜單的基礎。因此,作為程序員,你有責任確保提供有關如何使用程序的明確說明。

到此,相信大家對“Python進階版定義類時應用的最佳做法有哪些”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關內(nèi)容可以進入相關頻道進行查詢,關注我們,繼續(xù)學習!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI