歡迎你們前往騰訊雲+社區,獲取更多騰訊海量技術實踐乾貨哦~javascript
做者:朱雷 | 騰訊IEG高級工程師java
我一直以爲編程某種意義上是一門『手藝』,由於優雅而高效的代碼,就如同完美的手工藝品同樣讓人賞心悅目。python
在雕琢代碼的過程當中,有大工程:好比應該用什麼架構、哪一種設計模式。也有更多的小細節,好比什麼時候使用異常(Exceptions)、或怎麼給變量起名。那些真正優秀的代碼,正是由無數優秀的細節造就的。django
『Python 工匠』這個系列文章,是個人一次小小嚐試。它專一於分享 Python 編程中的一些偏**『小』**的東西。但願可以幫到每一位編程路上的匠人。編程
做爲『Python 工匠』系列文章的第一篇,我想先談談 『變量(Variables)』。由於如何定義和使用變量,一直都是學習任何一門編程語言最早要掌握的技能之一。設計模式
變量用的好或很差,和代碼質量有着很是重要的聯繫。在關於變量的諸多問題中,爲變量起一個好名字尤爲重要。數組
在計算機科學領域,有一句著名的格言(俏皮話):緩存
There are only two hard things in Computer Science: cache invalidation and naming things. 在計算機科學領域只有兩件難事:緩存過時 和 給東西起名字 -- Phil Karlton架構
第一個『緩存過時問題』的難度不用多說,任何用過緩存的人都會懂。至於第二個『給東西起名字』這事的難度,我也是深有體會。在個人職業生涯裏,度過的做爲黑暗的下午之一,就是坐在顯示器前抓耳撓腮爲一個新項目起一個合適的名字。
編程時起的最多的名字,還數各類變量。給變量起一個好名字很重要,由於好的變量命名能夠極大提升代碼的總體可讀性。
下面幾點,是我總結的爲變量起名時,最好遵照的基本原則。
在可接受的長度範圍內,變量名能把它所指向的內容描述的越精確越好。因此,儘可能不要用那些過於寬泛的詞來做爲你的變量名:
全部學習 Python 的人都知道,Python 是一門動態類型語言,它(至少在 PEP 484 出現前)沒有變量類型聲明。因此當你看到一個變量時,除了經過上下文猜想,無法輕易知道它是什麼類型。
不過,人們對於變量名和變量類型的關係,一般會有一些直覺上的約定,我把它們總結在了下面。
布爾類型變量的最大特色是:它只存在兩個可能的值**『是』** 或 『不是』。因此,用 is、has 等非黑即白的詞修飾的變量名,會是個不錯的選擇。原則就是:讓讀到變量名的人以爲這個變量只會有『是』或『不是』兩種值。
下面是幾個不錯的示例:
人們看到和數字相關的名字,都會默認他們是 int/float 類型,下面這些是比較常見的:
**注意:**不要使用普通的複數來表示一個 int 類型變量,好比 apples、trips,最好用 number_of_apples、trips_count 來替代。
對於 str、list、tuple、dict 這些複雜類型,很難有一個統一的規則讓咱們能夠經過名字去猜想變量類型。好比 headers,既多是一個頭信息列表,也多是包含頭信息的 dict。
對於這些類型的變量名,最推薦的方式,就是編寫規範的文檔,在函數和方法的 document string 中,使用 sphinx 格式(Python 官方文檔使用的文檔工具)來標註全部變量的類型。
第一次知道『匈牙利命名法』,是在 Joel on Software 的一篇博文中。簡而言之,匈牙利命名法就是把變量的『類型』縮寫,放到變量名的最前面。
關鍵在於,這裏說的變量『類型』,並不是指傳統意義上的 int/str/list 這種類型,而是指那些和你的代碼業務邏輯相關的類型。
好比,在你的代碼中有兩個變量:students 和 teachers,他們指向的內容都是一個包含 Person 對象的 list 。使用『匈牙利命名法』後,能夠把這兩個名字改寫成這樣:
students -> pl_students teachers -> pl_teachers
其中 pl 是 person list 的首字母縮寫。當變量名被加上前綴後,若是你看到以 pl_ 打頭的變量,就能知道它所指向的值類型了。
不少狀況下,使用『匈牙利命名法』是個不錯的主意,由於它能夠改善你的代碼可讀性,尤爲在那些變量衆多、同一類型屢次出現時。注意不要濫用就好。
在前面,咱們提到要讓變量名有描述性。若是不給這條原則加上任何限制,那麼你頗有可能寫出這種描述性極強的變量名:how_much_points_need_for_level2。若是代碼中充斥着這種過長的變量名,對於代碼可讀性來講是個災難。
一個好的變量名,長度應該控制在 兩到三個單詞左右。好比上面的名字,能夠縮寫爲 points_level2。
絕大多數狀況下,都應該避免使用那些只有一兩個字母的短名字,好比數組索引三劍客 i、j、k,用有明確含義的名字,好比 persion_index 來代替它們老是會更好一些。
有時,上面的原則也存在一些例外。當一些意義明確可是較長的變量名重複出現時,爲了讓代碼更簡潔,使用短名字縮寫是徹底能夠的。可是爲了下降理解成本,同一段代碼內最好不要使用太多這種短名字。
好比在 Python 中導入模塊時,就會常常用到短名字做爲別名,像 Django i18n 翻譯時經常使用的 gettext 方法一般會被縮寫成 _ 來使用*(from django.utils.translation import ugettext as _)*
其餘一些給變量命名的注意事項:
前面講了如何爲變量取一個好名字,下面咱們談談在平常使用變量時,應該注意的一些小細節。
若是你在一個方法內裏面把圖片變量叫作 photo,在其餘的地方就不要把它改爲 image,這樣只會讓代碼的閱讀者困惑:『image 和 photo 究竟是不是同一個東西?』
另外,雖然 Python 是動態類型語言,但那也不意味着你能夠用同一個變量名一會表示 str 類型,過會又換成 list。同一個變量名指代的變量類型,也須要保持一致性。
也許你第一次發現 globals()/locals() 這對內建函數時很興奮,火燒眉毛的寫下下面這種極端『簡潔』的代碼:
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() 節約了三行代碼,我是個天才!
return render(request, 'trip.html', locals())
複製代碼
千萬不要這麼作,這樣只會讓讀到這段代碼的人(包括三個月後的你本身)痛恨你,由於他須要記住這個函數內定義的全部變量(想一想這個函數增加到兩百行會怎麼樣?),更別提 locals() 還會把一些沒必要要的變量傳遞出去。
更況且, The Zen of Python(Python 之禪) 說的清清楚楚:Explicit is better than implicit.(顯式優於隱式)。因此,仍是老老實實把代碼寫成這樣吧:
return render(request, 'trip.html', {
'user': user,
'trip': trip,
'is_suggested': is_suggested
})
複製代碼
這個原則屬於老生常談了。不少人(包括我)在剛開始學習編程時,會有一個習慣。就是把全部的變量定義寫在一塊兒,放在函數或方法的最前面。
def generate_trip_png(trip):
path = []
markers = []
photo_markers = []
text_markers = []
marker_count = 0
point_count = 0
... ...
複製代碼
這樣作只會讓你的代碼『看上去很整潔』,可是對提升代碼可讀性沒有任何幫助。
更好的作法是,讓變量定義儘可能靠近使用。那樣當你閱讀代碼時,能夠更好的理解代碼的邏輯,而不是費勁的去想這個變量究竟是什麼、哪裏定義的?
Python 的函數能夠返回多個值:
def latlon_to_address(lat, lon):
return country, province, city
# 利用多返回值一次解包定義多個變量
country, province, city = latlon_to_address(lat, lon)
複製代碼
可是,這樣的用法會產生一個小問題:若是某一天, latlon_to_address 函數須要返回『城區(District)』時怎麼辦?
若是是上面這種寫法,你須要找到全部調用 latlon_to_address 的地方,補上多出來的這個變量,不然 ValueError: too many values to unpack 就會找上你:
country, province, city, district = latlon_to_address(lat, lon)
# 或者使用 _ 忽略多出來的返回值
country, province, city, _ = latlon_to_address(lat, lon)
複製代碼
對於這種可能變更的多返回值函數,使用 namedtuple/dict 會更方便一些。當你新增返回值時,不會對以前的函數調用產生任何破壞性的影響:
# 1. 使用 dict
def latlon_to_address(lat, lon):
return {
'country': country,
'province': province,
'city': city
}
addr_dict = latlon_to_address(lat, lon)
# 2. 使用 namedtuple
from collections import namedtuple
Address = namedtuple("Address", ['country', 'province', 'city'])
def latlon_to_address(lat, lon):
return Address(
country=country,
province=province,
city=city
)
addr = latlon_to_address(lat, lon)
複製代碼
不過這樣作也有壞處,由於代碼對變動的兼容性雖然變好了,可是你不能繼續用以前 x, y = f() 的方式一次解包定義多個變量了。取捨在於你本身。
人腦的能力是有限的,研究代表,人類的短時間記憶只能同時記住不超過十個名字。因此,當你的某個函數過長(通常來講,超過一屏的的函數就會被認爲有點過長了),包含了太多變量時。請及時把它拆分爲多個小函數吧。
這條原則很是簡單,也很容易作到。可是若是沒有遵照,那它對你的代碼質量的打擊是毀滅級的。會讓閱讀你代碼的人有一種被愚弄的感受。
def fancy_func():
# 讀者心理:嗯,這裏定義了一個 fancy_vars
fancy_vars = get_fancy()
... ...(一大堆代碼事後)
# 讀者心理:這裏就結束了?以前的 fancy_vars 去哪了?被貓吃了嗎?
return result
複製代碼
因此,請打開 IDE 的智能提示,及時清理掉那些定義了可是沒有使用的變量吧。
有時候,咱們定義變量時的心理活動是這樣的:『嗯,這個值將來說不定會修改/二次使用』,讓咱們先把它定義成變量吧!
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
複製代碼
其實,你所想的『將來』永遠不會來,這段代碼裏的三個臨時變量徹底能夠去掉,變成這樣:
def get_best_trip_by_user_id(user_id):
return {
'user': get_user(user_id),
'trip': get_best_trip(user_id)
}
複製代碼
沒有必要爲了那些可能出現的變更,犧牲代碼當前的可讀性。若是之後有定義變量的需求,那就之後再加吧。
碎碎唸了一大堆,不知道有多少人可以堅持到最後。變量做爲程序語言的重要組成部分,值得咱們在定義和使用它時,多花一丁點時間思考一下,那樣會讓你的代碼變得更優秀。
這是『Python 工匠』系列文章的第一篇,不知道看完文章的你,有沒有什麼想吐槽的?請留言告訴我吧。
**問答 **
相關閱讀
此文已由做者受權騰訊雲+社區發佈,更多原文請點擊
搜索關注公衆號「雲加社區」,第一時間獲取技術乾貨,關注後回覆1024 送你一份技術課程大禮包!
海量技術實踐經驗,盡在雲加社區!