『Python 工匠』是什么?
我一直覺得編程某種意義上是一門『手藝』,因?yàn)閮?yōu)雅而高效的代碼,就如同完美的手工藝品一樣讓人賞心悅目。
在雕琢代碼的過程中,有大工程:比如應(yīng)該用什么架構(gòu)、哪種設(shè)計(jì)模式。也有更多的小細(xì)節(jié),比如何時(shí)使用異常(Exceptions)、或怎么給變量起名。那些真正優(yōu)秀的代碼,正是由無數(shù)優(yōu)秀的細(xì)節(jié)造就的。
『Python 工匠』這個(gè)系列文章,是我的一次小小嘗試。它專注于分享 Python 編程中的一些偏『小』的東西。希望能夠幫到每一位編程路上的匠人。
變量和代碼質(zhì)量
作為『Python 工匠』系列文章的第一篇,我想先談?wù)?『變量(Variables)』。因?yàn)槿绾味x和使用變量,一直都是學(xué)習(xí)任何一門編程語言最先要掌握的技能之一。
變量用的好或不好,和代碼質(zhì)量有著非常重要的聯(lián)系。在關(guān)于變量的諸多問題中,為變量起一個(gè)好名字尤其重要。
如何為變量起名
在計(jì)算機(jī)科學(xué)領(lǐng)域,有一句著名的格言(俏皮話):
There are only two hard things in Computer Science: cache invalidation and naming things. 在計(jì)算機(jī)科學(xué)領(lǐng)域只有兩件難事:緩存過期 和 給東西起名字
— Phil Karlton
第一個(gè)『緩存過期問題』的難度不用多說,任何用過緩存的人都會(huì)懂。至于第二個(gè)『給東西起名字』這事的難度,我也是深有體會(huì)。在我的職業(yè)生涯里,度過的作為黑暗的下午之一,就是坐在顯示器前抓耳撓腮為一個(gè)新項(xiàng)目起一個(gè)合適的名字。
編程時(shí)起的最多的名字,還數(shù)各種變量。給變量起一個(gè)好名字很重要,因?yàn)楹玫淖兞棵梢詷O大的提高代碼整體可讀性。
下面幾點(diǎn),是我總結(jié)的為變量起名時(shí),最好遵守的基本原則。
1. 變量名要有描述性,不能太寬泛
在可接受的長(zhǎng)度范圍內(nèi),變量名能把它所指向的內(nèi)容描述的越精確越好。所以,盡量不要用那些過于寬泛的詞來作為你的變量名:
GOOD:day_of_week,hosts_to_reboot,expired_cards
BAD:day,host,cards,temp
2. 變量名最好讓人能猜出類型
老司機(jī)們都知道,Python 是一門動(dòng)態(tài)類型語言,它(至少在PEP 484出現(xiàn)前)沒有變量類型聲明。所以當(dāng)你看到一個(gè)變量時(shí),除了通過上下文猜測(cè),沒法輕易知道它是什么類型。
不過,人們對(duì)于變量名和變量類型的關(guān)系,通常會(huì)有一些直覺上的約定,我把它們總結(jié)在了下面。
『什么樣的名字會(huì)被當(dāng)成 bool 類型?』
布爾類型變量的最大特點(diǎn)是:它只存在兩個(gè)可能的值『是』或『不是』。所以,用is、has等非黑即白的詞修飾的變量名,會(huì)是個(gè)不錯(cuò)的選擇。原則就是:讓讀到變量名的人覺得這個(gè)變量只會(huì)有『是』或『不是』兩種值。
下面是幾個(gè)不錯(cuò)的示例:
is_superuser:『是否超級(jí)用戶』,只會(huì)有兩種值:是/不是
has_error:『有沒有錯(cuò)誤』,只會(huì)有兩種值:有/沒有
allow_vip:『是否允許 VIP』,只會(huì)有兩種值:允許/不允許
use_msgpack:『是否使用 msgpack』,只會(huì)有兩種值:使用/不使用
debug:『是否開啟調(diào)試模式』,被當(dāng)做 bool 主要是因?yàn)榧s定俗成
『什么樣的名字會(huì)被當(dāng)成 int/float 類型?』
人們看到和數(shù)字相關(guān)的名字,都會(huì)默認(rèn)他們是 int/float 類型,下面這些是比較常見的:
釋義為數(shù)字的所有單詞,比如:port(端口號(hào))、age(年齡)、radius(半徑)等等
使用 _id 結(jié)尾的單詞,比如:user_id、host_id
使用 length/count 開頭或者結(jié)尾的單詞,比如:length_of_username、max_length、users_count
注意:不要使用普通的復(fù)數(shù)來表示一個(gè) int 類型變量,比如apples、trips,最好用number_of_apples、trips_count來替代。
其他類型
對(duì)于 str、list、tuple、dict 這些復(fù)雜類型,很難有一個(gè)統(tǒng)一的規(guī)則讓我們可以通過名字去猜測(cè)變量類型。比如headers,既可能是一個(gè)頭信息列表,也可能是包含頭信息的 dict。
對(duì)于這些類型的變量名,最推薦的方式,就是編寫規(guī)范的文檔,在函數(shù)和方法的 document string 中,使用 sphinx 格式(Python 官方文檔使用的文檔工具)來標(biāo)注所有變量的類型。
3. 適當(dāng)使用『匈牙利命名法』
第一次知道『匈牙利命名法』,是在Joel on Software 的一篇博文中。簡(jiǎn)而言之,匈牙利命名法就是把變量的『類型』縮寫,放到變量名的最前面。
關(guān)鍵在于,這里說的變量『類型』,并非指?jìng)鹘y(tǒng)意義上的 int/str/list 這種類型,而是指那些和你的代碼業(yè)務(wù)邏輯相關(guān)的類型。
比如,在你的代碼中有兩個(gè)變量:students和teachers,他們指向的內(nèi)容都是一個(gè)包含 Person 對(duì)象的 list 。使用『匈牙利命名法』后,可以把這兩個(gè)名字改寫成這樣:
students ->pl_studentsteachers ->pl_teachers
pl 是person list的首字母縮寫。變量名被加上前綴后,當(dāng)你看到以pl_打頭的變量時(shí),就能知道它所指向的值類型了。
很多情況下,使用『匈牙利命名法』是一個(gè)不錯(cuò)的注意,它可以改善你的代碼可讀性,尤其在那些變量眾多、同一類型多次出現(xiàn)時(shí)。注意不要濫用就好。
4. 變量名盡量短,但是絕對(duì)不要太短
在前面,我們提到要讓變量名有描述性。如果不給這條原則加上任何限制,那么你很有可能寫出這種描述性極強(qiáng)的變量名:how_much_points_need_for_level2。如果代碼中充斥著這種過長(zhǎng)的變量名,對(duì)于代碼可讀性來說是個(gè)災(zāi)難。
一個(gè)好的變量名,長(zhǎng)度應(yīng)該控制在兩到三個(gè)單詞左右。比如上面的名字,可以縮寫為points_level2。
絕大多數(shù)情況下,都應(yīng)該避免使用那些只有一兩個(gè)字母的短名字,比如數(shù)組索引三劍客i、j、k,用有明確含義的名字,比如 persion_index 來代替它們總是會(huì)更好一些。
使用短名字的例外情況
有時(shí),不能使用短名字的原則也會(huì)有一些例外。當(dāng)一些意義明確但是較長(zhǎng)的變量名重復(fù)出現(xiàn)時(shí),為了讓代碼更簡(jiǎn)潔,使用短名字縮寫是完全可以的。但是為了降低理解成本,同一段代碼內(nèi)最好不要使用太多這種短名字。
比如在 Python 中導(dǎo)入模塊時(shí),就會(huì)經(jīng)常用到短名字作為別名,像 Django i18n 翻譯時(shí)常用的gettext方法通常會(huì)被縮寫成_來使用(from django.utils.translation import ugettext as _)
5. 其他注意事項(xiàng)
其他一些給變量命名的注意事項(xiàng):
同一段代碼內(nèi)不要使用過于相似的變量名,比如同時(shí)出現(xiàn)users、users1、user3這種序列
不要使用帶否定含義的變量名,用is_special代替is_not_normal
更好的使用變量
前面講了如何為變量取一個(gè)好名字,下面我們談?wù)勗谌粘J褂米兞繒r(shí),應(yīng)該注意的一些小細(xì)節(jié)。
1. 保持一致性
如果你在一個(gè)方法內(nèi)里面把圖片變量叫做photo,在其他的地方就不要把它改成image,這樣只會(huì)讓代碼的閱讀者困惑:『image和photo到底是不是同一個(gè)東西?』
另外,雖然 Python 是動(dòng)態(tài)類型語言,但那也不意味著你可以用同一個(gè)變量名一會(huì)表示 str 類型,過會(huì)又換成 list。同一個(gè)變量名指代的變量類型,也需要保持一致性。
2. 盡量不要用 globals()/locals()
也許你第一次發(fā)現(xiàn) globals()/locals() 這對(duì)內(nèi)建函數(shù)時(shí)很興奮,迫不及待的寫下下面這種極端『簡(jiǎn)潔』的代碼:
def render(request, user_id, trip_id): user = User.objects.get(id=user_id) trip = get_object_or_404(Trip, pk=trip_id) is_suggested = is_suggested(user, trip) # 利用 locals() 節(jié)約了三行代碼,我是個(gè)天才! return render(request, 'trip.html', locals())
千萬不要這么做,這樣只會(huì)讓讀到這段代碼的人(包括三個(gè)月后的你自己)痛恨你,因?yàn)樗枰涀∵@個(gè)函數(shù)內(nèi)定義的所有變量(想想這個(gè)函數(shù)增長(zhǎng)到兩百行會(huì)怎么樣?),更別提 locals() 還會(huì)把一些不必要的變量傳遞出去。
更何況,The Zen of Python(Python 之禪)說的清清楚楚:Explicit is better than implicit.(顯式優(yōu)于隱式)。還是老老實(shí)實(shí)把代碼改成這樣吧:
return render(request, 'trip.html', { 'user': user, 'trip': trip, 'is_suggested': is_suggested })
3. 變量定義盡量靠近使用
這個(gè)原則屬于老生常談了。很多人(包括我)在剛開始學(xué)習(xí)編程時(shí),會(huì)有一個(gè)習(xí)慣。就是把所有的變量定義寫在一起,放在函數(shù)或方法的最前面。
def generate_trip_png(trip): path = [] markers = [] photo_markers = [] text_markers = [] marker_count = 0 point_count = 0 ... ...
這樣做只會(huì)讓你的代碼『看上去很整潔』,但是對(duì)提高代碼可讀性沒有任何幫助。
更好的做法是,讓變量定義盡量靠近使用。那樣當(dāng)你閱讀代碼時(shí),可以更好的理解代碼的邏輯,而不是費(fèi)勁的去想這個(gè)變量到底是什么、哪里定義的?
4. 合理使用 dict 來讓函數(shù)返回多個(gè)值
Python 的函數(shù)可以返回多個(gè)值:
def latlon_to_address(lat, lon): return country, province, city # 利用多返回值一次定義多個(gè)變量country, province, city = latlon_to_address(lat, lon)
但是,這樣的用法會(huì)產(chǎn)生一個(gè)小問題:如果某一天,latlon_to_address函數(shù)需要返回『城區(qū)(District)』時(shí)怎么辦?
如果是上面這種寫法,你需要找到所有調(diào)用latlon_to_address的地方,補(bǔ)上多出來的這個(gè)變量,否則ValueError: too many values to unpack就會(huì)找上你:
country, province, city, district = latlon_to_address(lat, lon)# 或者忽略多出來的返回值country, province, city, _ = latlon_to_address(lat, lon)
對(duì)于這種多返回值可能會(huì)變動(dòng)的情況,使用 dict 作為返回值會(huì)更方便一些,當(dāng)你新增返回值時(shí),不會(huì)對(duì)之前的函數(shù)調(diào)用產(chǎn)生任何破壞性的影響:
def latlon_to_address(lat, lon): return { 'country': country, 'province': province, 'city': city } addr_dict = latlon_to_address(lat, lon)
這樣做的壞處也有,代碼兼容性雖然增加了,但是你不能繼續(xù)用之前x, y = f()的方式一次定義多個(gè)變量了。取舍在于你自己。
5. 控制單個(gè)函數(shù)內(nèi)的變量數(shù)量
人腦的能力是有限的,研究表明,人類的短期記憶只能同時(shí)記住不超過十個(gè)名字。所以,當(dāng)你的某個(gè)函數(shù)過長(zhǎng)(一般來說,超過一屏的的函數(shù)就會(huì)被認(rèn)為有點(diǎn)過長(zhǎng)了),包含了太多變量時(shí)。請(qǐng)及時(shí)把它拆分為多個(gè)小函數(shù)吧。
6. 及時(shí)刪掉那些沒用的變量
這條原則非常簡(jiǎn)單,也很容易做到。但是如果沒有遵守,那它對(duì)你的代碼質(zhì)量的打擊是毀滅級(jí)的。會(huì)讓閱讀你代碼的人有一種被愚弄的感覺。
def fancy_func(): # 讀者心理:嗯,這里定義了一個(gè) fancy_vars fancy_vars = get_fancy() ... ...(一大堆代碼過后) # 讀者心理:這里就結(jié)束了?之前的 fancy_vars 去哪了?被貓吃了嗎? return result
所以,請(qǐng)打開 IDE 的智能提示,及時(shí)清理掉那些定義了但是沒有使用的變量吧。
7. 能不定義變量就不定義
有時(shí)候,我們定義變量時(shí)的心理活動(dòng)是這樣的:『嗯,這個(gè)值未來說不定會(huì)修改/二次使用』,讓我們先把它定義成變量吧!
def get_best_trip_by_user_id(user_id): user = get_user(user_id) trip = get_best_trip(user_id) result = { 'user': user, 'trip': trip } return result
其實(shí),你所想的『未來』永遠(yuǎn)不會(huì)來,這段代碼里的三個(gè)臨時(shí)變量完全可以去掉,變成這樣:
def get_best_trip_by_user_id(user_id): return { 'user': get_user(user_id), 'trip': get_best_trip(user_id) }
沒有必要為了那些可能出現(xiàn)的變動(dòng),犧牲代碼當(dāng)前的可讀性。如果以后有定義變量的需求,那就以后再加吧。
結(jié)語
碎碎念了一大堆,不知道有多少人能夠堅(jiān)持到最后。變量作為程序語言的重要組成部分,值得我們?cè)诙x和使用它時(shí),多花一丁點(diǎn)時(shí)間思考一下,那樣會(huì)讓你的代碼變得更優(yōu)秀。
-
編程
+關(guān)注
關(guān)注
88文章
3689瀏覽量
95193 -
變量
+關(guān)注
關(guān)注
0文章
614瀏覽量
28938 -
python
+關(guān)注
關(guān)注
56文章
4827瀏覽量
86663
原文標(biāo)題:Python 工匠:善用變量來改善代碼質(zhì)量
文章出處:【微信號(hào):magedu-Linux,微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
python環(huán)境變量的配置pip
python環(huán)境變量設(shè)置

Python的常量與變量基礎(chǔ)知識(shí)詳解
python設(shè)置環(huán)境變量
Python私有變量的定義方法
使用Python實(shí)現(xiàn)水質(zhì)量接口調(diào)用的代碼實(shí)例免費(fèi)下載
Python的變量類型詳細(xì)資料說明

評(píng)論