Python編程高級技巧| 選擇好的名稱
點擊標題下「非同步圖書」可快速關注
大部分標準庫在構建時都要考慮可用性。例如,內置類型的使用是很自然的,其設計非常易於使用。在這種情況下,Python可以與你開發程序時所思考的偽代碼進行比較。大部分代碼都可以大聲朗讀出來。例如,任何人都可以理解下面這個代碼片段:
my_list = []
if d not in my_list: my_list.append(d)這就是編寫Python比編寫其他語言更加簡單的原因之一。在編寫程序時,你的思路可以快速轉換成代碼。
本文重點介紹編寫易於理解和使用的代碼的最佳實踐,包括:
- 使用PEP 8描述的命名約定。
- 一組命名最佳實踐。
- 常用工具的簡要介紹,這些工具可以讓你檢查是否遵守風格指南
本文摘自《Python高級編程(第2版)》第4章
轉發本文到朋友圈截圖給「非同步圖書後台」,並在文末留言說出你對本文的感想,12.15日我們將選出2名讀者贈送本書。
4.1 PEP 8與命名最佳實踐
PEP 8為編寫Python代碼提供了一個風格指南。除了空格縮進、每行最大長度以及其他與代碼布局有關的細節等基本規則之外,PEP 8還介紹了大部分代碼庫所遵循的命名約定。
本節給出了這一PEP的簡要總結,並進一步給出了每種元素的命名最佳實踐指南。但你仍然必須閱讀PEP 8文檔。
4.1.1 為何要遵守PEP 8以及何時遵守PEP 8
如果你正在創建一個打算開源的新軟體包,那麼答案很簡單:始終遵守。PEP 8實際上是大多數Python開源軟體的標準代碼風格。如果你想接受來自其他程序員的任何協作,即使你對最佳代碼風格指南有不同的看法,那麼也應該堅持遵守PEP 8。這樣做的好處是,其他程序員可以更容易地直接上手你的項目。對於新人來說,代碼更容易閱讀,因為它的風格與大多數其他Python開源包一致。
此外,開始時完全遵守PEP 8,可以讓你在未來省時省事。如果你想向公眾發布你的代碼,最終其他程序員也會建議你切換到PEP 8。關於對某一特定項目是否真有必要這麼做的爭吵,可能會變成一場永無止境並且永遠沒有贏家的口水戰(flame war)。這是令人悲傷的事實,但為了不失去貢獻者,你最終可能還是會被迫與這種風格保持一致。
而且,如果整個項目的代碼庫處於成熟的開發狀態,那麼對其重新調整風格(restyling)可能需要做大量的工作。在某些情況下,重新調整風格可能需要修改幾乎每行代碼。雖然大多數修改可以自動化完成(縮進、換行和行尾空格),但這種大規模的代碼檢查通常會給所有基於分支的版本控制工作流程引入許多衝突。同時審查這麼多修改也很困難。基於上述原因,許多開源項目都有一條規則:風格修改應該始終包含在單獨的拉取/合併(pull/merge)請求或補丁中,而不影響任何功能或bug。
4.1.2 超越PEP 8——團隊的風格指南
儘管PEP 8提供了一套全面的風格指南,但仍為開發者留有一些自由,特別是在嵌套數據字面量與需要很長的參數列表的多行函數調用方面。有些團隊可能會認為他們需要額外的風格規則,最好的做法就是正式發布某種文件供所有團隊成員使用。
此外,在某些情況下,對於沒有定義風格指南的一些老項目,嚴格遵守PEP 8可能在經濟上不可行。這樣的項目仍然可以從正式發布的編碼約定中受益,即使這些約定中沒有體現PEP 8的官方規則。要記住,比遵守PEP 8更重要的是項目內的一致性。如果有正式發布的規則供每名程序員參考,那麼在項目內和組織內保持一致性就簡單多了。
4.2 命名風格
Python中使用的不同命名風格包括以下幾種。
- 駝峰式命名法(CamelCase)。
- 混合式命名法(mixedCase)。
- 大寫(UPPERCASE)或大寫加下劃線(UPPERCASEWITH_UNDERSCORES)。
- 前綴(leading)和後綴(trailing)下劃線,有時是雙下劃線(doubled)。
小寫元素和大寫元素通常是一個單詞,有時是幾個單詞連在一起。使用下劃線的通常是縮寫短語。使用一個單詞要更好一些。前綴和後綴下劃線用於標記私有元素和特殊元素。
這些風格被應用到以下幾種情形。
- 變數。
- 函數和方法。
property
。- 類。
- 模塊。
- 包。
變數
Python中有兩種變數:
- 常量。
- 公有和私有變數。
1.常量
對於常量全局變數,使用大寫加下劃線。它告訴開發人員,指定的變數表示一個常數值。
Python中沒有像C++中那樣真正的常量——在C++中可以使用
const
。你可以修改任何變數的值。這就是Python使用命名約定將一個變數標記為常量的原因。
舉個例子,doctest
模塊提供了一系列選項標記和指令(https://docs.python.org/3.5/ library/doctest.html#option-flags),它們都是短小的句子,清晰地定義了每個選項的用途:
from doctest import IGNORE_EXCEPTION_DETAIL
from doctest import REPORT_ONLY_FIRST_FAILURE這些變數名稱看起相當長,但清晰地描述它們也很重要。它們主要在初始化代碼中使用,而不在代碼主體中使用,所以這種冗長的名稱並不會令人厭煩。
大部分情況下,縮寫名稱都會使代碼含義變得模糊。如果縮寫不夠清晰,不要害怕使用完整的單詞。
有些常量的名稱也是由底層技術驅動的。例如,os
模塊使用C中定義的一些常量,例如EX_XXX
系列定義了Unix退出代碼編號。例如,同樣的名稱代碼可以在系統的C頭文件sysexits.h
中找到,如下所示:
import os
import sys sys.exit(os.EX_SOFTWARE)使用常量的另一個好的做法是,將它們集中放在使用它們的模塊頂部,如果它們要用於下列操作,那麼就將其組合在新的變數中:
import doctest
TEST_OPTIONS = (doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE | doctest.REPORT_ONLY_FIRST_FAILURE)2.命名和使用
常量用來定義程序所依賴的一組值,例如默認配置文件名。
好的做法是將所有常量集中放在包中的一個文件內。舉個例子,Django採用的就是這種方法。一個名為settings.py
的模塊提供所有常量,如下所示:
# config.py SQL_USER = tarek
SQL_PASSWORD = secret SQL_URI = postgres://%s:%s@localhost/db % ( SQL_USER, SQL_PASSWORD ) MAX_THREADS = 4另一種方法是使用可以被ConfigParser
模塊或類似ZConfig
(Zope中用於描述其配置文件的解析器)之類的高級工具解析的配置文件。但有些人認為,對於Python這種文件能夠像文本文件一樣輕鬆編輯和修改的語言來說,使用另一種文件格式可能是過分之舉。
對於表現得像標記的選項,通常的做法是將它們和布爾運算結合起來,就像doctest
和re
模塊所做的那樣。doctest
中的模式很簡單,如下所示:
OPTIONS = {}
def register_option(name): return OPTIONS.setdefault(name, 1 << len(OPTIONS)) def has_option(options, name): return bool(options & name) # 現在定義選項 BLUE = register_option(BLUE) RED = register_option(RED) WHITE = register_option(WHITE)你將會得到下列結果:
>>> # 我們來嘗試一下 >>> SET = BLUE | RE
D >>> has_option(SET, BLUE) True >>> has_option(SET, WHITE) False
在創建這樣一組新的常量時,應避免對它們使用共同的前綴,除非模塊中有多組常量。模塊名稱本身就是一個共同的前綴。另一種解決方法是使用內置enum
模塊的Enum
類,並且依賴於set
集合類型而不是二進位運算符。不幸的是,Enum
類在面向舊Python版本的代碼中應用有限,因為enum
模塊由Python 3.4版提供。
在Python中,使用二進位按位運算來合併選項是很常見的。使用OR(
|
)運算符可以將多個選項合併到一個整數中,而使用AND(&amp;
)運算符則可以檢查該選項是否在整數中(參見has_option
函數)。
3.公有和私有變數
對於可變的且可以通過導入自由訪問的全局變數,如果它們需要被保護,那麼應該使用帶一個下劃線的小寫字母。但這種變數不經常使用,因為如果它們需要被保護,模塊通常會提供getter和setter來處理。在這種情況下,一個前綴下劃線可以將變數標記為包的私有元素,如下所示:
_observers = []
def add_observer(observer): _observers.append(observer) def get_observers(): ""確保_observers不能被修改。""" return tuple(_observers)位於函數和方法中的變數遵循相同的規則,並且永遠不會被標記為私有,因為它們對上下文來說是局部變數。
對於類或實例變數而言,只在將變數作為公有簽名的一部分不會帶來任何有用信息或者冗餘的情況下,才必須使用私有標記符(前綴下劃線)。
換句話說,如果變數在方法內部使用,用來提供公有功能,並且只具有這個功能,那麼最好將其設為私有。
例如,支持property
的屬性是很好的私有成員,如下所示:
class Citizen(object): def __init__(self):
self._message = Rosebud... def _get_message(self): return self._message kane = property(_get_message)另一個例子是用來記錄內部狀態的變數。這個值對其他代碼沒有用處,但卻參與了類的行為,如下所示:
class UnforgivingElephant(object): def __init__(self, name):
self.name = nameself._people_to_stomp_on = []
def get_slapped_by(self, name): self._people_to_stomp_on.append(name) print(Ouch!) def revenge(self): print(10 years later...) for person in self._people_to_stomp_on: print(%s stomps on %s % (self.name, person))下面是在互動式會話中的運行結果:
>>> joe = UnforgivingElephant(Joe) >>> joe.get_slapped_by(Tarek)
Ouch!
>>> joe.get_slapped_by(Bill)Ouch!>>> joe.revenge()10 years later... Joe stomps on TarekJoe stomps on Bill4.函數和方法
函數和方法的名稱應該使用小寫加下劃線。但在舊的標準庫模塊中並不總是這樣。Python 3對標準庫做了大量重組,所以大多數函數和方法都有一致的大小寫。不過對於某些模塊(例如threading
)而言,你可以訪問使用混合大小寫(mixedCase)的舊的函數名稱(例如currentThread
)。留著它們是為了更容易向後兼容,但如果你不需要在舊版Python中運行代碼,那麼應該避免使用這些舊的名稱。
這種方法的寫法在小寫範式成為標準之前很常見,一些框架(例如Zope和Twisted)對方法也使用混合大小寫。使用它的開發者社區仍然相當多。因此,選擇混合大小寫還是小寫加下劃線,這取決於你所使用的庫。
作為Zope的開發人員,保持一致性並不容易,因為構建一個混合純Python模塊和導入了Zope代碼的模塊的應用程序很困難。在Zope中,有一些類混用了兩種約定,因為代碼庫仍在發展,而Zope開發人員想要採用多數人都接受的常用約定。
在這種類型的程序庫環境中,得體的做法是只對暴露到框架中的元素使用混合大小寫,而其他代碼保持遵守PEP 8風格。
還值得注意的是,Twisted項目的開發人員採用一種完全不同的方法來解決這個問題。與Zope一樣,Twisted項目早於PEP 8文檔。項目啟動時沒有任何代碼風格的官方指南,所以它有自己的風格指南。關於縮進、文檔字元串、每行長度等風格規則可以很容易被採用。另一方面,修改所有代碼以匹配PEP 8的命名約定,可能會完全破壞向後兼容。對於像Twisted這樣的大型項目而言,這麼做並不可行。因此Twisted儘可能遵守PEP 8,並將其餘內容(例如變數、函數和方面的混合大小寫)作為它自己的編碼標準的一部分。這與PEP 8的建議完全兼容,因為它特彆強調,在項目內的一致性比遵守PEP 8風格指南要更加重要。
5.關於私有元素的爭論
對於私有方法和函數,慣例是添加一個前綴下劃線。考慮到Python中的名稱修飾(name-mangling)特性,這條規則是相當有爭議的。如果一個方法有兩個前綴下劃線,它會在運行時被解釋器重命名,以避免與任何子類中的方法產生命名衝突。
因此,有些人傾向於對私有屬性使用雙前綴下劃線,以避免子類中的命名衝突:
class Base(object): def __secret(self):
print("dont tell") def public(self): self.__secret() class Derived(Base): def __secret(self): print("never ever")你將會看到以下內容:
>>> Base.__secret
Traceback (most recent call last): File "<input>", line 1, in <module>AttributeError: type object Base has no attribute __secret
>>> dir(Base) [_Base__secret, ..., public]
>>> Derived().public() dont tellPython中名稱修飾的最初目的不是提供類似C++的私有花招(gimmick),而是用來確保某些基類隱式地避免子類中的衝突,特別是在多重繼承的上下文中。但將其用於每個屬性則會使私有代碼含義變得模糊,這一點也不Pythonic。
因此,有些人認為應該始終使用顯式的名稱修飾:
class Base: def _Base_secret(self): # 不要這麼做!!!
print("you told it ?")這樣會在所有代碼中重複類名,所以應該首選__
。
但正如BDFL(Guido,the Benevolent Dictator For Life,參見http://en.wikipedia.org/wiki/ BDFL)所說,最佳做法是在編寫子類中的方法之前查看該類的__mro__
(方法解析順序)值,從而避免使用名稱修飾。修改基類的私有方法一定要小心。
關於這個主題的更多信息,許多年前在Python-Dev郵件列表中出現過一個有趣的討論,人們爭論名稱修飾的實用性以及它在這門語言中的命運。你可以訪問地網址:http://mail.python.org/ pipermail/python-dev/2005-December/058555.html查看。
6.特殊方法
特殊方法(https://docs.python.org/3/reference/datamodel.html#special-method-names)以雙下劃線開始和結束,常規的方法不應該使用這種約定。有些開發者曾經將其稱為dunder方法,作為雙下劃線(double-underscore)的合成詞。它們可用於運算符重載、容器定義等方面。為了保證可讀性,它們應該集中放在類定義的開頭:
class WeirdInt(int): def __add__(self, other):
return int.__add__(self, other) + 1 def __repr__(self): return <weirdo %d> % self # 公共API def do_this(self): print(this) def do_that(self): print(that)對於常規方法而言,你永遠不應該使用這種名稱。所以不要為方法創建這樣的名稱:
class BadHabits: def __my_method__(self):
print(ok)7.參數
參數名稱使用小寫,如果需要的話可以加下劃線。它們遵循與變數相同的命名規則。
8.property
property
的名稱使用小寫或小寫加下劃線。大部分時候,它們表示一個對象的狀態,可以是名詞或形容詞,如果需要的話也可以是如下簡短的短語:
class Connection:
_connected = [] def connect(self, user): self._connected.append(user) @property def connected_people(self): return , .join(self._connected)在互動式會話中運行的結果如下所示:
>>> connection = Connection() >>> connection.connect(Tarek)
>>> connection.connect(Shannon) >>> print(connection.connected_people)Tarek, Shannon9.類
類名稱始終採用駝峰式命名法,如果它們是模塊的私有類,還可能有一個前綴下劃線。
類和實例變數通常是名詞短語,與用動詞短語命名的方法名稱構成使用邏輯:
class Database: def open(self): pass class User: pass
下面是在互動式會話中的使用示例:
>>> user = User() >>> db = Database() >>> db.open()
10.模塊和包
除了特殊模塊__init__
之外,模塊名稱都使用小寫,不帶下劃線。
下面是標準庫中的一些例子:
os
;sys
;shutil
。
如果模塊是包的私有模塊,則添加一個前綴下劃線。編譯過的C或C++模塊名稱通常帶有一個下劃線,並在純Python模塊中導入。
包名稱遵循同樣的規則,因為它的表現就像是更加結構化的模塊。
4.3 命名指南
一組常用的命名規則可以被應用於變數、方法、函數和property
。類和模塊的名稱也在命名空間的構建中具有重要的作用,從而也影響代碼可讀性。本迷你指南為挑選名稱提供了常見的模式和反模式。
4.3.1 用「has」或「is」前綴命名布爾元素
如果一個元素保存的是布爾值,is
和has
前綴提供一種自然的方式,使其在命名空間中的可讀性更強,代碼如下:
class DB:
is_connected = False has_cache = False4.3.2 用複數形式命名集合變數
如果一個元素保存的是集合變數,那麼使用複數形式是一個好主意。有些映射在暴露為序列時也可以從中受益:
class DB:
connected_users = [Tarek] tables = { Customer: [id, first_name, last_name] }4.3.3 用顯式名稱命名字典
如果一個變數保存的是映射,那麼你應該儘可能使用顯式名稱。例如,如果一個字典保存的是個人地址,那麼可以將其命名為persons_addresses
,代碼如下:
persons_addresses = {Bill: 6565 Monty Road,
Pamela: 45 Python street} persons_addresses[Pamela] 45 Python street4.3.4 避免通用名稱
如果你的代碼不是在構建一種新的抽象數據類型,那麼使用類似list
、dict
、sequence
或elements
等專用名詞是有害的,即使對於局部變數也一樣。它使得代碼難以閱讀、理解和使用。還應該避免使用內置名稱,以避免在當前命名空間中將其屏蔽(shadowing)。還應該避免使用通用的動詞,除非它們在該命名空間中有意義。
相反,應該使用領域特定的術語,如下所示:
def compute(data): # 太過通用 for element in data:
yield element ** 2 def squares(numbers): # 更好一些 for number in numbers: yield number ** 2還有一系列前綴和後綴,雖然在編程中非常常見,但事實上應該避免出現在函數和類名稱中:
- manager;
- object;
- do、handle或perform。
這樣做的原因是它們的含義模糊、模稜兩可,且沒有向實際名稱中添加任何信息。Jeff Atwood(Discourse 和Stack Overflow的聯合創始人)關於這個話題寫過一篇非常好的文章,你可以在他的博客上找到這篇文章:http://blog.codinghorror.com/i-shall-call-it-somethingmanager/。
還有許多包的名稱應該避免。任何沒有對其內容給出任何信息的名稱,從長遠來看都對項目有很大害處。諸如misc
、tools
、utils
、common
或core
的名稱有很大可能會變成一大堆不相關的、質量非常差的代碼片段,其大小呈指數增長。在大多數情況下,這種模塊的存在是懶惰或缺乏足夠設計經驗的跡象。熱衷於這種模塊名稱的人可以預見未來,並將其重命名為trash
(垃圾箱)或dumpster
(垃圾桶),因為這正是他們的隊友最終對這些模塊的處理方式。
在大多數情況下,更多的小模塊幾乎總是更好,即使內容很少,但名稱可以很好地反映其內容。說實話,類似utils
和common
之類的名稱沒有本質錯誤,你可以負責任地使用它們。但現實表明,在許多情況下,它們都會變為危險的結構反模式,並且迅速增長。而且如果你的行動不夠快,你可能永遠無法擺脫它們。因此,最好的方法就是避免這樣有風險的組織模式,如果項目其他人引入的話則將其扼殺在萌芽狀態。
4.3.5 避免現有名稱
使用上下文中已經存在的名稱是不好的做法,因為它會導致閱讀代碼時——特別是調試時——非常混亂,例如以下代碼:
>>> def bad_citizen():
... os = 1... import pdb; pdb.set_trace()... returnos...>>> bad_citizen()><stdin>(4)bad_citizen()(Pdb) os1(Pdb) import os (Pdb) c <module os from /Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/os.pyc>在這個例子中,os
名稱被代碼屏蔽。內置函數名稱和來自標準庫的模塊名稱都應該避免。
盡量使用原創的名稱,即使是上下文的局部名稱。對於關鍵字而言,後綴下劃線是一種避免衝突的方法:
def xapian_query(terms, or_=True): """if or_ is true, terms are combined with the OR clause"""
...注意,class
通常被替換為klass
或cls
:
def factory(klass, *args, **kwargs): return klass(*args, **kwargs)
4.4 參數的最佳實踐
函數和方法的簽名是代碼完整性的保證,它們驅動函數和方法的使用並構建其API。除了我們之前看到的命名規則之外,對參數也要特別小心。這可以通過3個簡單的規則來實現。
- 通過迭代設計構建參數。
- 信任參數和測試。
- 小心使用魔法參數
*args
和**kwargs
。
4.4.1 通過迭代設計構建參數
如果每個函數都有一個固定的、定義明確的參數列表,那麼代碼的魯棒性會更好。但這在第一個版本中無法完成,所以參數必須通過迭代設計來構建。它們應該反映創建該元素所針對的使用場景,並相應地逐漸發展。
例如,如果添加了一些參數,它們應該儘可能有默認值,以避免任何退化:
class Service: # 版本1 def _query(self, query, type)
: print(done) def execute(self, query): self._query(query, EXECUTE) >>> Service().execute(my query) done import logging class Service(object): # 版本2 def _query(self, query, type, logger): logger(done) def execute(self, query, logger=logging.info): self._query(query, EXECUTE, logger) >>> Service().execute(my query) # 舊式調用>>> Service().execute(my query, logging.warning)WARNING:root:done如果一個公共元素的參數必須被修改,那麼將使用一個deprecation進程,本節稍後將對此進行說明。
4.4.2 信任參數和測試
考慮到Python的動態類型特性,有些開發人員在函數和方法的頂部使用斷言(assertion)來確保參數具有正確的內容,代碼如下:
def division(dividend, divisor):
assert isinstance(dividend, (int, float)) assert isinstance(divisor, (int, float)) return dividend / divisor >>> division(2, 4) 0.5 >>> division(2, None)Traceback (most recent call last): File "<input>", line 1, in<module> File "<input>", line 3, in divisionAssertionError這通常是那些習慣於靜態類型、並且感覺Python中缺少點什麼的開發者的做法。
這種檢查參數的方法是契約式設計(Design by Contract,DbC,參見http://en.wikipedia.org/ wiki/DesignByContract)編程風格的一部分。在這種設計中,在代碼實際運行之間會檢查先決條件。
這種方法有兩個主要問題。
- DbC的代碼對應該如何使用它進行解釋,導致其可讀性降低。
- 這可能使代碼速度變慢,因為每次調用都要進行斷言。
後者可以通過解釋器的"-O"
選項來避免。在這種情況下,在創建位元組碼之前,所有斷言都將從代碼中刪除,這樣檢查也就會丟失。
在任何情況下,斷言都必須小心進行,並且不應該用於使Python變成一種靜態類型語言。唯一的使用場景就是保護代碼不被無意義地調用。
在大多數情況下,健康的測試驅動開發(TDD)風格可以提供魯棒性很好的基礎代碼。在這裡,功能測試和單元測試驗證了創建代碼所針對的所有使用場景。
如果庫中的代碼被外部元素使用,那麼進行斷言可能是有用的,因為傳入的數據可能會導致程序結束甚至造成破壞。這在處理資料庫或文件系統的代碼中可能發生。
另一種方法是模糊測試(fuzz testing,參見https://en.wikipedia.org/wiki/Fuzzing),它通過向程序發送隨機的數據塊來檢測其弱點。如果發現了新的缺陷,代碼會被修復以解決這一缺陷,並添加一次新的測試。
讓我們來關注一個遵循TDD方法的代碼庫,它向正確的方向發展,每當出現新的缺陷時都會對其進行調整,從而魯棒性越來越好。當它以正確的方式完成時,測試中的斷言列表在某種程度上變得類似於先決條件列表。
4.4.3 小心使用*args
和**kwargs
魔法參數
*args
和**kwargs
參數可能會破壞函數或方法的魯棒性。它們會使簽名變得模糊,而且代碼常常在不應該出現的地方構建小型的參數解析器,如下所示:
def fuzzy_thing(**kwargs):
if do_this in kwargs: print(ok i did) if do_that in kwargs: print(that is done) print(errr... ok)>>> fuzzy_thing(do_this=1) ok i did errr... ok >>> fuzzy_thing(do_that=1) that is done errr... ok >>> fuzzy_thing(hahaha=1) errr... ok如果參數列表變得很長而且很複雜,那麼添加魔法參數是很吸引人的。但這更表示它是一個脆弱的函數或方法,應該被分解或重構。
如果*args
被用於處理元素序列(在函數中以相同方式處理),那麼要求傳入唯一的容器參數(例如iterator
)會更好些,如下所示:
def sum(*args): # 可行
total = 0 for arg in args: total += arg return total def sum(sequence): # 更好! total = 0 for arg in sequence: total += arg return total**kwargs
適用於同樣的規則。最好固定命名參數,使方法簽名更有意義,如下所示:
def make_sentence(**kwargs):
noun = kwargs.get(noun, Bill) verb = kwargs.get(verb, is) adj = kwargs.get(adjective, happy) return %s %s %s % (noun, verb, adj) def make_sentence(noun=Bill, verb=is, adjective=happy): return %s %s %s % (noun, verb, adjective)另一種有趣的方法是創建一個容器類,將多個相關參數分組以提供執行上下文。這種結構與*args
或**kwargs
不同,因為它可以提供能夠操作數值並且能夠獨立發展的內部構件(internals)。使用它作為參數的代碼將不必處理其內部構件。
例如,傳入函數的Web請求通常由一個類實例表示。這個類負責保存Web伺服器傳入的數據,代碼如下:
def log_request(request): # 版本1
print(request.get(HTTP_REFERER, No referer)) def log_request(request): # 版本2 print(request.get(HTTP_REFERER, No referer)) print(request.get(HTTP_HOST, No host))魔法參數有時是無法避免的,特別是在元編程中。例如,想要創建能夠處理任何類型簽名的函數的裝飾器,它是不可或缺的。更普遍地說,在處理對函數進行遍歷的未知數據時,魔法參數都很好用,代碼如下:
import logging
def log(**context): http://logging.info(Context is:n%sn % str(context))4.5 類的名稱
類的名稱必須簡明、精確,並足以使人理解類的作用。常見的做法是使用後綴來表示其類型或特性。例如:
- SQLEngine;
- MimeTypes;
- StringWidget;
- TestCase。
對於基類或抽象類,可以使用一個Base或Abstract前綴,如下所示:
- BaseCookie;
- AbstractFormatter。
最重要的是要和類屬性保持一致。例如,盡量避免類及其屬性名稱之間的冗餘:
>>> SMTP.smtp_send() # 命名空間中存在冗餘信息 >>> SMTP.send() # 可讀性更強,也更易於記憶
4.6 模塊和包的名稱
模塊和包的名稱應體現其內容的目的。其名稱應簡短、使用小寫字母、並且不帶下劃線:
sqlite
;postgres
;sha1
。
如果它們實現一個協議,那麼通常會使用lib
後綴,代碼如下:
import smtplib import url
lib import telnetlib它們還需要在命名空間中保持一致,這樣使用起來更加簡單,代碼如下:
from widgets.stringwidgets import TextWidget # 不好 from widgets.strings import TextWidget # 更好
同樣,應該始終避免使用與標準庫模塊相同的名稱。
如果一個模塊開始變得複雜,並且包含許多類,那麼好的做法是創建一個包並將模塊的元素劃分到其他模塊中。
__init__
模塊也可以用於將一些API放回頂層,因為它不會影響使用,但有助於將代碼重新組織為更小的部分。例如,考慮foo
包中的__init__
模塊,其內容如下所示:
from .module1 import feature1, feature2
from .module2 import feature3這將允許用戶直接導入特性,如下列代碼所示:
from foo import feature1, feature2, feature3
但要注意,這可能會增加循環依賴的可能性,並且在__init__
模塊中添加的代碼將被實例化。所以要小心使用。
小結
本章通過Python官方風格指南(PEP 8文檔)來介紹廣受認可的編碼約定。除了官方風格指南,介紹了一些命名建議,可以讓你以後的代碼更加明確;還介紹了一些有用的工具,在保持代碼風格一致方面不可或缺。
所有這些內容都是為本書第一個實用主題做準備——編寫並分發Python包。下一章我們將學習如何在公共PyPI倉庫中發布我們自己的包,以及在私人組織中如何利用打包生態系統的力量。
Python高級編程(第2版)
- 作者: 【波蘭】Micha? Jaworski(賈沃斯基) , 【法】Tarek Ziadé(萊德)
- 分類: 軟體開發 > 編程語言 > Python
- 本書基於Python 3.5版本進行講解,通過13章的內容,深度揭示了Python編程的高級技巧。本書從Python語言及其社區的現狀開始介紹,對Python語法、命名規則、Python包的編寫、部署代碼、擴展程序開發、管理代碼、文檔編寫、測試開發、代碼優化、並發編程、設計模式等重要話題進行了全面系統化的講解。
- 本書適合想要進一步提高自身Python編程技能的讀者閱讀,也適合對Python編程感興趣的讀者參考學習。全書結合典型且實用的開發案例,可以幫助讀者創建高性能的、可靠且可維護的Python應用。
掃碼試讀更多Python圖書
延伸推薦
8本新書,送出一本你喜歡的
11月新書人氣王票選
一文讀懂Go的net/http標準庫
京東質量測試技術論壇:大咖雲集,非同步免費報名中!
你所不了解的深度神經網路
《Go Web編程》這一本不可錯過!
2017優秀圖書和作譯者評選-進行中贏取kindle等技術圖書禮!
AI經典書單| 入門人工智慧該讀哪些書?
聊聊Python
你所不了解的Kafka
Kotlin最佳項目實戰——歐瑞天氣App
點擊關鍵詞閱讀更多新書:
Python|機器學習|Kotlin|Java|移動開發|機器人|有獎活動|Web前端|書單
在「非同步圖書」後台回復「關注」,即可免費獲得2000門在線視頻課程;推薦朋友關注根據提示獲取贈書鏈接,免費得非同步圖書一本。趕緊來參加哦!
掃一掃上方二維碼,回復「關注」參與活動!
點擊閱讀原文,購買《Python高級編程(第2版)》
推薦閱讀:
※數據分析項目--如何選擇你的航班
※如何在十天內學會django達到可以找工作的水平?
※Python入門 遍歷循環 for...
※哪裡能找到 Python 視頻教程地址?
※量化策略系列教程:13布林強盜系統