編寫高質量Python程式(三)基礎語法

来源:https://www.cnblogs.com/monteyang/archive/2020/04/18/12728729.html
-Advertisement-
Play Games

本系列文章為《編寫高質量代碼——改善Python程式的91個建議》的精華彙總。 首發於公眾號【Python與演算法之路】 關於導入模塊 Python的3種引入外部模塊的方式: 語句、 和 函數。其中前兩種比較常見。 在使用 時,應註意: 優先使用 或 有節制的使用 儘量避免使用 對於 ,如果無節制的使 ...


本系列文章為《編寫高質量代碼——改善Python程式的91個建議》的精華彙總。

關於導入模塊

Python的3種引入外部模塊的方式:import語句、from ... import ...__import__函數。其中前兩種比較常見。

在使用 import 時,應註意:

  • 優先使用 import Aimport A as a
  • 有節制的使用 from A import B
  • 儘量避免使用 from A import *

對於 from a import ...,如果無節制的使用,會帶來的問題:

  • 命名空間的衝突
  • 迴圈嵌套導入的問題(兩個文件相互導入對方的變數或函數或類)

i += 1 不等於 ++i

Python 解釋器會將 ++i 解釋為 +(+i),其中 + 表示正數符號。對於 --i 也是類似。

因此,要明白 ++i 在 Python 的語法層面上是合法的,但並不是通常意義上的自增操作。

使用 with 自動關閉資源

對文件操作完成後,應該立即關閉它們,因為打開的文件不僅會占用系統資源,而且可能影響其他程式或者進程的操作,甚至會導致用戶期望與實際操作結果不一致。

Python 提供了 with 語句,語法為:

with 表達式 [as 目標]:
    代碼塊

with 語句支持嵌套,支持多個 with 子句,它們兩者可以相互轉換。with expr1 as e1, expr2 as e2 與下麵的嵌套形式等價:

with expr1 as e1:
    with expr2 as e2:

使用 else 子句簡化迴圈(異常處理)

在迴圈中, else 子句提供了隱含的對迴圈是否由 break 語句引發迴圈結束的判斷。例子:

# 以下兩段代碼等價
# 藉助了一個標誌量 found 來判斷迴圈結束是不是由 break 語句引起的。
def print_prime(n):
    for i in range(2, n):
        found = True
        for j in range(2, i):
            if i % j == 0:
                found = False
                break
        if found:
            print("{} is a prime number".format(i))

def print_prime2(n):
    for i in range(2, n):
        for j in range(2, i):
            if i % j == 0:
                break
        else:
            print("{} is a prime number".format(i))

當迴圈“自然”終結(迴圈條件為假)時 else 從句會被執行一次,而當迴圈是由 break 語句中斷時,else 子句就不被執行。

for 語句相似,while 語句中的 else 子句的語意是一樣的: else 塊在迴圈正常結束和迴圈條件不成立時被執行。

遵循異常處理的幾點基本原則

Python中常用的異常處理語法是tryexceptelsefinally,它們可以有多種組合。語法形式如下:

# Run this main action first
try:
    <statements>

# 當 try 中發生 name1 的異常時,進行處理
except <name1>:
    <statements>

# 當 try 中發生 name2 或 name3 中的某一個異常時
except (name2, name3):
    <statements>

# 當 try 中發生 name4 的異常時處理,並獲取對應實例
except <name4> as <data>:
    <statements>

# 其他異常時,進行處理
except:
    <statements>

# 沒有異常時,執行
else:
    <statements>

# 無論有沒有異常,都執行
finally:
    <statements>

異常處理,通常需要遵循以下幾點基本原則:

  • 不推薦在 try 中放入過多的代碼。在 try 中放入過多的代碼帶來的問題是如果程式中拋出異常,將會較難定位,給 debug 和修複帶來不便,因此應儘量只在可能拋出異常的語句塊前面放入 try 語句。
  • 謹慎使用單獨的 except 語句處理所有異常,最好能定位具體的異常。同樣也不推薦使用 except Exception 或者 except StandardError 來捕獲異常。如果必須使用,最好能夠使用 raise 語句將異常拋出向上層傳遞。
  • 註意異常捕獲的順序,在合適的層次處理異常。
    • 用戶也可以繼承自內建異常構建自己的異常類,從而在內建類的繼承結構上進一步延伸。在這種情況下捕獲異常的順序顯得非常重要。為了更精確地定位錯誤發生的原因,推薦的方法是將繼承結構中子類異常在前面的 except 語句中拋出,而父類異常在後面的 except 語句拋出。這樣做的原因是當 try 塊中有異常發生的時候,解釋器根據 except 聲明的順序進行匹配,在第一個匹配的地方便立即處理該異常。
    • 異常捕獲的順序非常重要,同時異常應該在適當的位置被處理,一個原則就是如果異常能夠在被捕獲的位置被處理,那麼應該及時處理,不能處理也應該以合適的方式向上層拋出。向上層傳遞的時候需要警惕異常被丟失的情況,可以使用不帶參數的 raise 來傳遞。
  • 使用更為友好的異常信息,遵守異常參數的規範。通常來說有兩類異常閱讀者:使用軟體的人和開發軟體的人。

避免 finally 中可能發生的陷阱

無論 try 語句中是否有異常拋出,finally 語句總會被執行。由於這個特性,finally 語句經常被用來做一些清理工作。
但使用 finally 時,也要特別小心一些陷阱。

  • try 塊中發生異常的時候,如果在 except 語句中找不到對應的異常處理,異常將會被臨時保存起來,當 finally 執行完畢的時候,臨時保存的異常將會再次被拋出,但如果 finally 語句中產生了新的異常或者執行了 return 或者 break 語句,那麼臨時保存的異常將會被丟失,從而導致異常屏蔽。
  • 在實際應用程式開發過程中,並不推薦在 finally 中使用 return 語句進行返回,這種處理方式不僅會帶來誤解而且可能會引起非常嚴重的錯誤。

深入理解 None,正確判斷對象是否為空

Python 中以下數據會當作空來處理:

  • 常量 None
  • 常量 False
  • 任何形式的數值類型零,如 00L0.00j
  • 空的序列,如 ''()[]
  • 空的字典,如 {}
  • 當用戶定義的類中定義了 __nonzero__()__len__() 方法,並且該方法返回整數 0False 的時候。
if list1 # value is not empty
    Do something
else: # value is empty
    Do some other thing
  • 執行過程中會調用內部方法 __nonzero__() 來判斷變數 list1 是否為空並返回其結果。

註: __nonzero__() 方法 —— 該內部方法用於對自身對象進行空值測試,返回 0/1 或 True/False。

  • 如果一個對象沒有定義該方法,Python 將獲取 __len__() 方法調用的結果來進行判斷。__len__() 返回值為 0 則表示為空。如果一個類中既沒有定義 __len__() 方法也沒有定義 __nonzero__() 方法,該類的實例用 if 判斷的結果都為 True。

格式化字元串時儘量使用 .format 方式而不是 %

推薦儘量使用 format 方式而不是 % 操作符來格式化字元串,理由:

  • format 方式在使用上較 % 操作符更為靈活。使用 format 方式時,參數的順序與格式化的順序不必完全相同

  • format 方式可以方便的作為參數傳遞

    weather = [("Monday", "rain"), ("Tuesday", "sunny"), ("Wednesday", "sunny"), ("Thursday", "rain"), ("Friday", "cloudy")]
    formatter = "Weather of '{0[0]}' is '{0[1]}'".format
    for item in map(formatter, weather):
        print(item)
    
  • % 最終會被 .format 方式所代替。根據 Python 的官方文檔,之所以仍然保留 % 操作符是為了保持向後相容

  • % 方法在某些特殊情況下使用時需要特別小心,對於 % 直接格式化字元的這種形式,如果字元本身為元組,則需要使用在 % 使用 (itemname,) 這種形式才能避免錯誤,註意逗號。

區別對待可變對象和不可變對象

Python 中一切皆對象,對象根據其值能否修改分為可變對象不可變對象

  • 不可變對象

    • 數字
    • 字元串
    • 元組
  • 可變對象

    • 字典
    • 列表
    • 位元組數組

在將可變對象作為函數預設參數的時候要特別緊惕,對可變對象的更改會直接影響原對象。

最好的方法是傳入 None 作為預設參數,在創建對象的時候動態生成可變對象。

  • 對於一個可變對象,切片操作相當於淺拷貝。

  • 對於不可變對象,當我們對其進行相關操作的時候,Python 實際上仍然保持原來的值而且重新創建一個新的對象,所以字元串對象不允許以索引的方式進行賦值,當有兩個對象同時指向一個字元串對象的時候,對其中一個對象的操作並不會影響另一個對象。

函數傳參既不是傳值也不是傳引用

對於Python中函數的傳參方法,既不是傳值,也不是傳引用

正確的叫法應該是傳對象(call by object)或者說傳對象的引用(call-by-object-reference)。

函數參數在傳遞的過程中將整個對象傳入,

  • 對於可變對象:它的修改在函數外部以及內部都可見,調用者和被調用者之間共用這個對象
  • 對於不可變對象:由於並不能真正被修改,因此,修改往往是通過生成一個新對象然後賦值來實現的

慎用變長參數

慎用可變長度參數*args, **kwargs,原因如下:

  • 使用過於靈活。變長參數意味著這個函數的簽名不夠清晰,存在多種調用方式。另外變長參數可能會破壞程式的健壯性。
  • 如果一個函數的參數列表很長,雖然可以通過使用 *args**kwargs 來簡化函數的定義,但通常這個函數可以有更好的實現方式,應該被重構。例如可以直接傳入元組和字典。

可變長參數適合在下列情況下使用:

  • 為函數添加一個裝飾器
  • 如果參數的數目不確定,可以考慮使用變長參數
  • 用來實現函數的多態,或者在繼承情況下子類需要調用父類的某些方法的時候

深入理解 str()repr() 的區別

函數 str()repr() 都可以將 Python 中的對象轉換為字元串,兩者的使用以及輸出都非常相似。有以下幾點區別:

  • 兩者的目標不同:

    • str() 主要面向用戶,其目的是可讀性,返回形式為用戶友好性和可讀性都較強的字元串類型
    • repr() 面向開發人員,其目的是準確性,其返回值表示 Python 解釋器內部的含義,常用作 debug
  • 在解釋器中直接輸入時預設調用 repr() 函數,而 print 則調用 str() 函數

  • repr() 的返回值一般可以用 eval() 函數來還原對象。通常有如下等式:obj == eval(repr(obj))

  • 一般,類中都應該定義 __repr__() 方法,而 __str__() 方法則為可選,當可讀性比準確性更為重要的時候應該考慮定義 __str__() 方法。如果類中沒有定義 __str__() 方法,則預設會使用 __repr__() 方法的結果來返回對象的字元串表示形式。用戶實現 __repr__() 方法的時,最好保證其返回值可以用 eval() 方法使對象重新還原。

分清靜態方法和類方法的適用場景

靜態方法:

class C(object):
    @staticmethod
    def f(arg1, arg2, ...):

類方法:

class C(object):
    @classmethod
    def f(cls, arg1, arg2, ...):

都可以通過類名.方法名或者實例.方法名的形式來訪問。

其中,靜態方法沒有常規方法的特殊行為,如綁定、非綁定、隱式參數等規則,而類方法的調用使用類本身作為其隱含參數,但調用本身並不需要顯示提供該參數。

類方法

  • 在調用的時候沒有顯式聲明 cls,但實際上類本身是作為隱藏參數傳入的
  • 類方法可以判斷出自己是通過基類被調用,還是通過某個子類被調用
  • 類方法通過子類調用時,可以返回子類的屬性而非基類的屬性
  • 類方法通過子類調用時,可以調用子類的其他類方法

靜態方法

  • 既不跟特定的實例相關也不跟特定的類相關
  • 靜態方法定義在類中的原因是,能夠更加有效地將代碼組織起來,從而使相關代碼的垂直距離更近,提高代碼的可維護性

文章首發於公眾號【Python與演算法之路】


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 環境:chrome 80 演習:用 `Generator`封裝$.ajax Promise 第一次請求成功,接著請求第二次 多個請求全部成功,才執行下一步操作 多個請求,全部執行完畢後進行操作 Generator 第一次請求成功,接著請求第二次 多個請求全部成功,才執行下一步操作 ...
  • 現在使用的js語法,基本是ES5的規範 ,15年出的ES6的規範增加了很多其他語法,要看瀏覽器的支持情況,如果瀏覽器不支持那麼就會報錯 ES6 塊級作用域 關鍵字let, 常量const,對象字面量的屬性賦值簡寫,賦值解構,函數參數 - 預設值、參數打包、 數組展開(Default 、Rest 、S ...
  • 這是一組非常容易弄混的參數!都是描述某個盒子元素的寬度、高度以及上或左的距離偏移量。 1. offsetWidth / offsetHeight(不包括外邊距) offsetWidth:返回元素的寬度(content+padding+border) offsetHeight:返回元素的高度(cont ...
  • p5.js完成星際穿越特效 歡迎關註我的 "博客" ,⬅️點他即可。 星際穿越,是模仿漫天星辰撲面而來的感覺。 最關鍵的在於對透視的掌握。 參考資料:The Coding Train 00 思路構想 1. 星星是一個圓,會隨機的出現在屏幕的任何位置; 2. 星星會從遠處到眼前: 圓的大小 來表示遠近 ...
  • 這次作業完成了一個開環可選層電梯調度系統。第二次迭代加入了容量限制、多部電梯,第三次迭代加入了電梯樓層分工、增添電梯請求。 1. 系統架構 MainClass用於對各個子系統的組裝,發送請求至Schedule Schedule用於接收來自MainClass、Executor的信息,更新狀態 Exec ...
  • 在整體應用架構中,非生產環境情況下,一般 1GB 或者 2GB 的 RAM 就足夠了。如果我們將這個應用程式劃分為 20 或 30 個獨立的微服務,那麼很難期望 RAM 仍將保持在 1GB 或 2GB 左右。特別是如果我們使用 Spring Cloud 的時候。 首先,準備三個服務,Eureka 服 ...
  • 通過本次講座,我想強調的是,You!Leaders!一定要通過層層疊加的“Rules”建立起本能反應,一遇到類似的事情,應激般的就知道該怎麼設計,怎麼行動,怎麼救火。 而這些“Rules”是經歷了血與火的洗禮鑄造的,每一條都有來由有去路。 等有一天你依據本能(也就是你自建的法則)行事的時候,你就會體... ...
  • 更多精彩文章請關註公眾號『大海的BLOG』 問題 話說大詩人李白,一生好飲。幸好他從不開車。 一天,他提著酒壺,從家裡出來,酒壺中有酒兩鬥。他邊走邊唱: 無事街上走,提壺去打酒。 逢店加一倍,遇花喝一鬥。 這一路上,他一共遇到店 5 次,遇到花 10 次,已知最後一次遇到的是花,他正好把酒喝光了。請 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...