當前位置:
首頁 > 知識 > Python 工匠:善用變數來改善代碼質量

Python 工匠:善用變數來改善代碼質量

(點擊

上方公眾號

,可快速關注)




來源: 本文來自作者 piglei 的投稿 


http://python.jobbole.com/85702/




『Python 工匠』是什麼?

我一直覺得編程某種意義上是一門『手藝』,因為優雅而高效的代碼,就如同完美的手工藝品一樣讓人賞心悅目。


在雕琢代碼的過程中,有大工程:比如應該用什麼架構、哪種設計模式。也有更多的小細節,比如何時使用異常(Exceptions)、或怎麼給變數起名。那些真正優秀的代碼,正是由無數優秀的細節造就的。


『Python 工匠』這個系列文章,是我的一次小小嘗試。它專註於分享 Python 編程中的一些偏

『小』

的東西。希望能夠幫到每一位編程路上的匠人。


變數和代碼質量


作為『Python 工匠』系列文章的第一篇,我想先談談 『變數(Variables)』。因為如何定義和使用變數,一直都是學習任何一門編程語言最先要掌握的技能之一。


變數用的好或不好,和代碼質量有著非常重要的聯繫。在關於變數的諸多問題中,為變數起一個好名字尤其重要。


如何為變數起名


在計算機科學領域,有一句著名的格言(俏皮話):



There are only two hard things in Computer Science: cache invalidation and naming things. 在計算機科學領域只有兩件難事:緩存過期 和 給東西起名字


— Phil Karlton


第一個『緩存過期問題』的難度不用多說,任何用過緩存的人都會懂。至於第二個『給東西起名字』這事的難度,我也是深有體會。在我的職業生涯里,度過的作為黑暗的下午之一,就是坐在顯示器前抓耳撓腮為一個新項目起一個合適的名字。


編程時起的最多的名字,還數各種變數。給變數起一個好名字很重要,

因為好的變數命名可以極大的提高代碼整體可讀性。


下面幾點,是我總結的為變數起名時,最好遵守的基本原則。


1. 變數名要有描述性,不能太寬泛


可接受的長度範圍內

,變數名能把它所指向的內容描述的越精確越好。所以,盡量不要用那些過於寬泛的詞來作為你的變數名:




  • GOOD

    day_of_week

    hosts_to_reboot

    expired_cards



  • BAD

    day

    host

    cards

    temp


2. 變數名最好讓人能猜出類型


老司機們都知道,Python 是一門動態類型語言,它(至少在 

PEP 484

 出現前)沒有變數類型聲明。所以當你看到一個變數時,除了通過上下文猜測,沒法輕易知道它是什麼類型。


不過,人們對於變數名和變數類型的關係,通常會有一些直覺上的約定,我把它們總結在了下面。

『什麼樣的名字會被當成 bool 類型?』


布爾類型變數的最大特點是:它只存在兩個可能的值

『是』

 或 

『不是』

。所以,用 

is

has

等非黑即白的詞修飾的變數名,會是個不錯的選擇。原則就是:

讓讀到變數名的人覺得這個變數只會有『是』或『不是』兩種值


下面是幾個不錯的示例:




  • is_superuser

    :『是否超級用戶』,只會有兩種值:是/不是



  • has_error

    :『有沒有錯誤』,只會有兩種值:有/沒有



  • allow_vip

    :『是否允許 VIP』,只會有兩種值:允許/不允許



  • use_msgpack

    :『是否使用 msgpack』,只會有兩種值:使用/不使用



  • debug

    :『是否開啟調試模式』,被當做 bool 主要是因為約定俗成


『什麼樣的名字會被當成 int/float 類型?』


人們看到和數字相關的名字,都會默認他們是 int/float 類型,下面這些是比較常見的:




  • 釋義為數字的所有單詞,比如:

    port(埠號)

    age(年齡)

    radius(半徑)

     等等



  • 使用 _id 結尾的單詞,比如:

    user_id

    host_id



  • 使用 length/count 開頭或者結尾的單詞,比如: 

    length_of_username

    max_length

    users_count


注意:

不要使用普通的複數來表示一個 int 類型變數,比如 

apples

trips

,最好用 

number_of_apples

trips_count

 來替代。


其他類型


對於 str、list、tuple、dict 這些複雜類型,很難有一個統一的規則讓我們可以通過名字去猜測變數類型。比如 

headers

,既可能是一個頭信息列表,也可能是包含頭信息的 dict。


對於這些類型的變數名,最推薦的方式,就是編寫規範的文檔,在函數和方法的 document string 中,使用 sphinx 格式(

Python 官方文檔使用的文檔工具

)來標註所有變數的類型。


3. 適當使用『匈牙利命名法』


第一次知道『

匈牙利命名法

』,是在 

Joel on Software 的一篇博文

中。簡而言之,匈牙利命名法就是把變數的『類型』縮寫,放到變數名的最前面。


關鍵在於,這裡說的變數『類型』,並非指傳統意義上的 int/str/list 這種類型,而是指那些和你的代碼業務邏輯相關的類型。


比如,在你的代碼中有兩個變數:

students

 和 

teachers

,他們指向的內容都是一個包含 Person 對象的 list 。使用『匈牙利命名法』後,可以把這兩個名字改寫成這樣:


students -> 

pl_students

 teachers -> 

pl_teachers


pl 是 

person list

 的首字母縮寫。變數名被加上前綴後,當你看到以 

pl_

 打頭的變數時,就能知道它所指向的值類型了。


很多情況下,使用『匈牙利命名法』是一個不錯的注意,它可以改善你的代碼可讀性,尤其在那些變數眾多、同一類型多次出現時。注意不要濫用就好。


4. 變數名盡量短,但是絕對不要太短


在前面,我們提到要讓變數名有描述性。如果不給這條原則加上任何限制,那麼你很有可能寫出這種描述性極強的變數名:

how_much_points_need_for_level2

。如果代碼中充斥著這種過長的變數名,對於代碼可讀性來說是個災難。


一個好的變數名,長度應該控制在 

兩到三個單詞左右

。比如上面的名字,可以縮寫為 

points_level2


絕大多數情況下,都應該避免使用那些只有一兩個字母的短名字

,比如數組索引三劍客 

i

j

k

,用有明確含義的名字,比如 persion_index 來代替它們總是會更好一些。


使用短名字的例外情況


有時,不能使用短名字的原則也會有一些例外。當一些意義明確但是較長的變數名重複出現時,為了讓代碼更簡潔,使用短名字縮寫是完全可以的。但是為了降低理解成本,同一段代碼內最好不要使用太多這種短名字。


比如在 Python 中導入模塊時,就會經常用到短名字作為別名,像 Django i18n 翻譯時常用的 

gettext

 方法通常會被縮寫成 

_

 來使用(from django.utils.translation import ugettext as _)


5. 其他注意事項


其他一些給變數命名的注意事項:




  • 同一段代碼內不要使用過於相似的變數名,比如同時出現 

    users

    users1

    、 

    user3

     這種序列



  • 不要使用帶否定含義的變數名,用 

    is_special

     代替 

    is_not_normal


更好的使用變數


前面講了如何為變數取一個好名字,下面我們談談在日常使用變數時,應該注意的一些小細節。


1. 保持一致性


如果你在一個方法內裡面把圖片變數叫做 

photo

,在其他的地方就不要把它改成 

image

,這樣只會讓代碼的閱讀者困惑:『

image

 和 

photo

 到底是不是同一個東西?』


另外,雖然 Python 是動態類型語言,但那也不意味著你可以用同一個變數名一會表示 str 類型,過會又換成 list。

同一個變數名指代的變數類型,也需要保持一致性。


2. 盡量不要用 globals()/locals()


也許你第一次發現 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


    

})




3. 變數定義盡量靠近使用


這個原則屬於老生常談了。很多人(包括我)在剛開始學習編程時,會有一個習慣。就是把所有的變數定義寫在一起,放在函數或方法的最前面。



def generate_trip_png

(

trip

)

:


    

path

=

[]


    

markers

=

[]


    

photo_markers

=

[]


    

text_markers

=

[]


    

marker_count

=

0


    

point_count

=

0


    

...

...




這樣做只會讓你的代碼『看上去很整潔』,但是對提高代碼可讀性沒有任何幫助。


更好的做法是,

讓變數定義盡量靠近使用

。那樣當你閱讀代碼時,可以更好的理解代碼的邏輯,而不是費勁的去想這個變數到底是什麼、哪裡定義的?


4. 合理使用 dict 來讓函數返回多個值


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

)




對於這種多返回值可能會變動的情況,使用 dict 作為返回值會更方便一些,當你新增返回值時,不會對之前的函數調用產生任何破壞性的影響:






def latlon_to_address

(

lat

,

lon

)

:


    

return

{


        

"country"

:

country

,


        

"province"

:

province

,


        

"city"

:

city


    

}


 


addr_dict

=

latlon_to_address

(

lat

,

lon

)




這樣做的壞處也有,代碼兼容性雖然增加了,但是你不能繼續用之前 x, y = f() 的方式一次定義多個變數了。取捨在於你自己。


5. 控制單個函數內的變數數量


人腦的能力是有限的,研究表明,人類的短期記憶只能同時記住不超過十個名字。所以,當你的某個函數過長(一般來說,超過一屏的的函數就會被認為有點過長了),包含了太多變數時。請及時把它拆分為多個小函數吧。


6. 及時刪掉那些沒用的變數


這條原則非常簡單,也很容易做到。但是如果沒有遵守,那它對你的代碼質量的打擊是毀滅級的。會讓閱讀你代碼的人有一種被愚弄的感覺。



def fancy_func

()

:


    

# 讀者心理:嗯,這裡定義了一個 fancy_vars


    

fancy_vars

=

get_fancy

()


    

...

...

(一大堆代碼過後)


 


    

# 讀者心理:這裡就結束了?之前的 fancy_vars 去哪了?被貓吃了嗎?


    

return

result




所以,請打開 IDE 的智能提示,及時清理掉那些定義了但是沒有使用的變數吧。


7. 能不定義變數就不定義


有時候,我們定義變數時的心理活動是這樣的:『嗯,這個值未來說不定會修改/二次使用』,讓我們先把它定義成變數吧!



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 工匠』系列文章的第一篇,不知道看完文章的你,有沒有什麼想吐槽的?請留言告訴我吧。




看完本文有收穫?請轉

發分享給更多人


關注「P

ython開發者」,提升Python技能


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python開發者 的精彩文章:

面向對象:元氣少女就是我!
Python 數據處理庫 pandas 入門教程

TAG:Python開發者 |