使用Python3實(shí)現(xiàn)判斷函數(shù)的圈復(fù)雜度
你有沒有見過那種長達(dá)幾百行、邏輯錯(cuò)綜復(fù)雜的“巨無霸”函數(shù)?那樣的函數(shù)不光難讀,改起來同樣困難重重,人人唯恐避之不及。
編寫函數(shù)最重要的原則就是:別寫太復(fù)雜的函數(shù)。那什么樣的函數(shù)才能算是過于復(fù)雜?一般會(huì)通過兩個(gè)標(biāo)準(zhǔn)來判斷,長度和圈復(fù)雜度。
長度
長度也就是函數(shù)有多少行代碼。不過不能武斷地說,長函數(shù)就一定比短函數(shù)復(fù)雜。因?yàn)樵诓煌木幊田L(fēng)格下,相同行數(shù)的代碼所實(shí)現(xiàn)的功能可以有巨大差別,有人甚至能把一個(gè)完整的俄羅斯方塊游戲塞進(jìn)一行代碼內(nèi)。
但即便如此,長度對于判斷函數(shù)復(fù)雜度來說仍然有巨大價(jià)值。在著作《代碼大全(第 2 版)》中,Steve McConnell 提到函數(shù)的理想長度范圍是 65 到 200 行,一旦超過 200 行,代碼出現(xiàn) bug 的概率就會(huì)顯著增加。
對于 Python 這種強(qiáng)表現(xiàn)力的語言來說,65 行已經(jīng)非常值得警惕了。假如你的函數(shù)超過 65 行,很大概率代表函數(shù)已經(jīng)過于復(fù)雜,承擔(dān)了太多職責(zé),請考慮將它拆分為多個(gè)小而簡單的子函數(shù)(類)吧。
圈復(fù)雜度
“圈復(fù)雜度”是由 Thomas J. McCabe 在 1976 年提出的用于評估函數(shù)復(fù)雜度的指標(biāo)。它的值是一個(gè)正整數(shù),代表程序內(nèi)線性獨(dú)立路徑的數(shù)量。圈復(fù)雜度的值越大,表示程序可能的執(zhí)行路徑就越多,邏輯就越復(fù)雜。
如果某個(gè)函數(shù)的圈復(fù)雜度超過10,就代表它已經(jīng)太復(fù)雜了,代碼編寫者應(yīng)該想辦法簡化。優(yōu)化寫法或者拆分成子函數(shù)都是不錯(cuò)的選擇。接下來,我們通過實(shí)際代碼來體驗(yàn)一下圈復(fù)雜度的計(jì)算過程。
在Python中,可以通過radon工具計(jì)算函數(shù)的圈復(fù)雜度。安裝命令:
pip3 install radon
假設(shè)我們有段代碼示例如下,實(shí)現(xiàn)的功能是猜數(shù)字游戲,里面有1個(gè)whilie和2個(gè)if-else分支判斷邏輯,文件名:complex_func.py。
import random
def guess_number():
# 生成一個(gè)隨機(jī)數(shù)作為答案
answer = random.randint(1, 100)
# 初始化猜測次數(shù)
guesses = 0
print("歡迎來到猜數(shù)字游戲!我已經(jīng)想好了一個(gè)1到100之間的數(shù)字,你需要猜出這個(gè)數(shù)字是多少。")
# 開始循環(huán),直到玩家猜中數(shù)字為止
while True:
# 獲取玩家的猜測
guess = int(input("請輸入你猜測的數(shù)字:"))
# 增加猜測次數(shù)
guesses += 1
# 檢查玩家猜測的數(shù)字與答案的關(guān)系
if guess < answer:
print("你猜的數(shù)字太小了,請繼續(xù)努力!")
elif guess > answer:
print("你猜的數(shù)字太大了,請?jiān)僭囈淮危?)
else:
print(f"恭喜你,你猜對了!答案是 {answer}。你一共猜了 {guesses} 次。")
break # 結(jié)束循環(huán)
# 調(diào)用函數(shù)開始游戲
guess_number()
接下來我們使用radon來計(jì)算這個(gè)文件對應(yīng)函數(shù)的圈復(fù)雜度,文件名:calculate_cyclomatic_complexity.py
from radon.complexity import cc_visit
# 定義一個(gè)Python文件路徑
file_path = 'complex_func.py'
# 使用cc_visit函數(shù)計(jì)算代碼的圈復(fù)雜度
with open(file_path, 'r') as file:
code = file.read()
results = cc_visit(code)
print(results)
# 打印結(jié)果
for result in results:
print(result)
執(zhí)行結(jié)果:可以看到函數(shù)圈復(fù)雜度為 4。
$ python3 calculate_cyclomatic_complexity.py
[Function(name='guess_number', lineno=3, col_offset=0, endline=27, is_method=False, classname=None, closures=[], complexity=4)]
F 3:0->27 guess_number - 4
我們接下來看另外一個(gè)完整的代碼示例,其中被計(jì)算的函數(shù)為rank(),功能是按照電影分?jǐn)?shù)計(jì)算評級,最后輸出了圈復(fù)雜度和對應(yīng)的評分等級,文件名:
get_film_score.py
import radon
from radon.complexity import cc_rank, cc_visit
def calculate_complexity(source_code):
"""
Calculate the cyclomatic complexity of the given source code.
Parameters:
source_code (str): The source code to analyze.
Returns:
int: The cyclomatic complexity.
str: The complexity rating.
"""
try:
# Visit the AST and calculate the complexity
results = cc_visit(source_code)
complexity = results[0].complexity
# Get the complexity rating
rating = cc_rank(complexity)
return complexity, rating
except Exception as e:
print("Error:", e)
return None, None
# Example usage:
if __name__ == "__main__":
code = """
def rank(self):
rating_num = float(self.rating)
if rating_num >= 8.5:
return 'S'
elif rating_num >= 8:
return 'A'
elif rating_num >= 7:
return 'B'
elif rating_num >= 6:
return 'C'
else:
return 'D'
"""
complexity, rating = calculate_complexity(code)
if complexity is not None and rating is not None:
print("Cyclomatic Complexity:", complexity)
print("Complexity Rating:", rating)
運(yùn)行結(jié)果:可以看到函數(shù)圈復(fù)雜度為 5,評級為 A。
雖然這個(gè)值沒有達(dá)到危險(xiǎn)線 10,但考慮到函數(shù)只有短短 10 行,5 已經(jīng)足夠引起重視了。
$ python3 get_film_score.py
Cyclomatic Complexity: 5
Complexity Rating: A
作為對比,我們再計(jì)算一下案例中使用bisect模塊重構(gòu)后的 rank() 函數(shù):
def rank(self):
breakpoints = (6, 7, 8, 8.5)
grades = ('D', 'C', 'B', 'A', 'S')
index = bisect.bisect(breakpoints, float(self.rating))
return grades[index]
運(yùn)行結(jié)果:可以看到函數(shù)圈復(fù)雜度為 1,評級為 A。
$ python3 get_film_score.py
Cyclomatic Complexity: 1
Complexity Rating: A
可以看到,新函數(shù)的圈復(fù)雜度從 5 降至 1。1 是一個(gè)非常理想化的值,如果一個(gè)函數(shù)的圈復(fù)雜度為 1,就代表這個(gè)函數(shù)只有一條主路徑,沒有任何其他執(zhí)行路徑,這樣的函數(shù)通常來說都十分簡單、容易維護(hù)。
當(dāng)然,在正常的項(xiàng)目開發(fā)流程中,我們一般不會(huì)在每次寫完代碼后,都手動(dòng)執(zhí)行一次 radon 命令檢查函數(shù)圈復(fù)雜度是否符合標(biāo)準(zhǔn),而會(huì)將這種檢查配置到開發(fā)或部署流程中自動(dòng)執(zhí)行。
到此這篇關(guān)于使用Python3實(shí)現(xiàn)判斷函數(shù)的圈復(fù)雜度的文章就介紹到這了,更多相關(guān)Python3函數(shù)圈復(fù)雜度內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用OpenCV circle函數(shù)圖像上畫圓的示例代碼
這篇文章主要介紹了使用OpenCV circle函數(shù)圖像上畫圓的示例代碼,本文內(nèi)容簡短,給大家突出重點(diǎn)內(nèi)容,需要的朋友可以參考下2019-12-12
python pandas中索引函數(shù)loc和iloc的區(qū)別分析
在數(shù)據(jù)分析過程中,很多時(shí)候我們需要從數(shù)據(jù)表中提取出我們需要的部分,而這么做的前提是我們需要先索引出這一部分?jǐn)?shù)據(jù),下面這篇文章主要給大家介紹了關(guān)于python pandas中索引函數(shù)loc和iloc區(qū)別的相關(guān)資料,需要的朋友可以參考下2021-09-09
使用Python從零實(shí)現(xiàn)一個(gè)最基礎(chǔ)的MCP協(xié)議
模型上下文協(xié)議是?Anthropic?在2024年提出的一種開放標(biāo)準(zhǔn)協(xié)議,用于標(biāo)準(zhǔn)化?AI?模型與外部工具和數(shù)據(jù)源的集成方式,本示例將展示如何使用?Python?實(shí)現(xiàn)一個(gè)最基礎(chǔ)的?MCP?協(xié)議,包括?MCP?服務(wù)器和?MCP?客戶端兩部分,需要的朋友可以參考下2026-04-04
python如何實(shí)現(xiàn)單鏈表的反轉(zhuǎn)
這篇文章主要介紹了python如何實(shí)現(xiàn)單鏈表的反轉(zhuǎn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02
如何用Python?實(shí)現(xiàn)景區(qū)安防系統(tǒng)
本設(shè)計(jì)中,利用YOLO目標(biāo)檢測算法、Openpose姿態(tài)識別算法、deepsort跟蹤算法、MSCNN人群密度估計(jì)算法實(shí)現(xiàn)了火災(zāi)監(jiān)測、吸煙監(jiān)測、行為安全監(jiān)測、人群密度監(jiān)測、口罩率監(jiān)測、人員定位監(jiān)測六大功能,對Python?實(shí)現(xiàn)景區(qū)安防系統(tǒng)感興趣的朋友一起看看吧2022-07-07
Python?numpy中np.random.seed()的詳細(xì)用法實(shí)例
在學(xué)習(xí)人工智能時(shí),大量的使用了np.random.seed(),利用隨機(jī)數(shù)種子,使得每次生成的隨機(jī)數(shù)相同,下面這篇文章主要給大家介紹了關(guān)于Python?numpy中np.random.seed()的詳細(xì)用法,需要的朋友可以參考下2022-08-08

