自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

編寫(xiě)干凈高效Python代碼的七個(gè)策略

譯文
開(kāi)發(fā) 前端
我們?cè)诒疚闹杏懻摿藰I(yè)界用于生產(chǎn)就緒代碼的一些最佳實(shí)踐。這些都是常見(jiàn)的行業(yè)實(shí)踐,可以緩解人們?cè)趯?shí)際情形中可能面臨的多個(gè)問(wèn)題。

譯者 | 布加迪

審校 | 重樓

是否曾經(jīng)將您的Python代碼與經(jīng)驗(yàn)豐富的開(kāi)發(fā)人員的代碼進(jìn)行比較,感到明顯的差異?盡管可以從在線資源學(xué)習(xí)Python,但初學(xué)者代碼和專家代碼之間通常存在差距。這是由于經(jīng)驗(yàn)豐富的開(kāi)發(fā)人員堅(jiān)持奉行社區(qū)確保的最佳實(shí)踐。這些實(shí)踐在在線教程中經(jīng)常被忽視,但對(duì)于大規(guī)模應(yīng)用程序而言至關(guān)重要。我在本文中將介紹生產(chǎn)級(jí)代碼中使用的7個(gè)技巧,確保代碼更清晰、更有條理。

1. 類型提示和注釋

Python是一種動(dòng)態(tài)類型的編程語(yǔ)言,在運(yùn)行時(shí)推斷變量類型。雖然它提高了靈活性,但在協(xié)作環(huán)境中大大降低了代碼的可讀性和可理解性。

Python支持函數(shù)聲明中的類型提示,充當(dāng)函數(shù)參數(shù)類型和返回類型的注釋。盡管Python在運(yùn)行時(shí)不強(qiáng)制執(zhí)行這些類型,但它仍然很有幫助,因?yàn)樗鼓拇a更容易被其他人和您自己理解。

從一個(gè)基本的例子開(kāi)始,這是一個(gè)帶類型提示的簡(jiǎn)單函數(shù)聲明:

Def sum(a: int, b: int) -> int:
Return a + b

在這里,盡管函數(shù)不言自明,但我們看到函數(shù)參數(shù)和返回值都表示為int類型。函數(shù)體可以是一行,也可以是幾百行。然而,我們只需查看函數(shù)聲明就能理解前置條件和返回類型。

其中,這些注釋只是為了清晰和指引,它們并不在執(zhí)行期間強(qiáng)制執(zhí)行類型。所以,即使您傳入不同類型的值,比如字符串而不是整數(shù),函數(shù)仍然會(huì)運(yùn)行。但是要小心:如果您不提供預(yù)期的類型,它可能會(huì)在運(yùn)行時(shí)導(dǎo)致意外的行為或錯(cuò)誤。比如在提供的示例中,函數(shù)sum()需要兩個(gè)整數(shù)作為參數(shù)。但是如果您嘗試添加一個(gè)字符串和一個(gè)整數(shù),Python會(huì)拋出運(yùn)行時(shí)錯(cuò)誤。為什么?因?yàn)樗恢廊绾螌⒆址驼麛?shù)相加!這就好比試圖把蘋(píng)果和橘子加在一起,那毫無(wú)意義。然而,如果兩個(gè)參數(shù)都是字符串,它將毫無(wú)問(wèn)題地將它們連接起來(lái)。

下面是帶有測(cè)試用例的澄清版本:

print(sum(2,5)) # 7
# print(sum('hello', 2)) # TypeError: can only concatenate str (not "int") to str
# print(sum(3,'world')) # TypeError: unsupported operand type(s) for +: 'int' and 'str'
print(sum('hello', 'world')) # helloworld

用于高級(jí)類型提示的typing庫(kù)

針對(duì)高級(jí)注釋,Python包含typing標(biāo)準(zhǔn)庫(kù)。不妨以一種更有趣的方式來(lái)看其用法。

from typing import Union, Tuple, List
import numpy as np
def sum(variable: Union[np.ndarray, List]) -> float:
	total = 0
	# function body to calculate the sum of values in iterable
	return total

這里,我們修改了同一個(gè)求和函數(shù),它現(xiàn)在接受numpy數(shù)組或列表iterable。它計(jì)算并以浮點(diǎn)值的形式返回它們的和。我們利用typing庫(kù)中的Union注釋來(lái)指定變量參數(shù)可以接受的可能類型。

不妨進(jìn)一步更改函數(shù)聲明,以顯示列表成員還應(yīng)該是類型float。

def sum(variable: Union[np.ndarray, List[float]]) -> float:
	total = 0
	# function body to calculate the sum of values in iterable
	return total

這些只是幫助理解Python中的類型提示的一些入門(mén)示例。隨著項(xiàng)目規(guī)模擴(kuò)大,代碼庫(kù)變得更模塊化,類型注釋顯著地增強(qiáng)了可讀性和可維護(hù)性。typing庫(kù)提供了一組豐富的特性,包括可選的各種iterable、泛型以有力支持自定義類型的功能,使開(kāi)發(fā)人員能夠精確而清晰地表達(dá)復(fù)雜的數(shù)據(jù)結(jié)構(gòu)和關(guān)系。

2. 編寫(xiě)防御函數(shù)和輸入驗(yàn)證

盡管類型提示似乎很有幫助,但它仍然容易出錯(cuò),因?yàn)槲磸?qiáng)制執(zhí)行注釋。這些只是開(kāi)發(fā)人員的額外文檔,但如果使用不同的參數(shù)類型,函數(shù)仍然會(huì)執(zhí)行。因此,需要以一種防御性的方式為函數(shù)和代碼強(qiáng)制執(zhí)行前置條件。因此,我們手動(dòng)檢查這些類型,違反條件時(shí)拋出適當(dāng)?shù)腻e(cuò)誤。

下面的函數(shù)顯示了如何使用輸入?yún)?shù)計(jì)算利息。

def calculate_interest(principal, rate, years):
	return principal * rate * years

這是簡(jiǎn)單的操作,但這個(gè)函數(shù)是否適用于所有可能的解決方案?不,不適用于無(wú)效值作為輸入傳遞的個(gè)別情況。我們需要確保輸入值在一個(gè)有效的范圍內(nèi),那樣函數(shù)才能正確執(zhí)行。實(shí)際上,必須滿足一些預(yù)設(shè)值條件,函數(shù)實(shí)現(xiàn)才能正確。

我們做這一步,如下所示:

from typing import Union
def calculate_interest(
	principal: Union[int, float],
	rate: float,
	years: int
) -> Union[int, float]:
	if not isinstance(principal, (int, float)):
    	    raise TypeError("Principal must be an integer or float")
	if not isinstance(rate, float):
    	    raise TypeError("Rate must be a float")
	if not isinstance(years, int):
    	    raise TypeError("Years must be an integer")
	if principal <= 0:
    	    raise ValueError("Principal must be positive")
	if rate <= 0:
    	    raise ValueError("Rate must be positive")
	if years <= 0:
    	    raise ValueError("Years must be positive")
	interest = principal * rate * years
	return interest

注意,我們使用條件語(yǔ)句進(jìn)行輸入驗(yàn)證。Python有時(shí)也用于此目的的斷言語(yǔ)句。然而,用于輸入驗(yàn)證的斷言并不是最佳實(shí)踐,因?yàn)樗鼈兒苋菀妆唤茫瑫?huì)導(dǎo)致生產(chǎn)環(huán)境中的意外行為。對(duì)于輸入驗(yàn)證和強(qiáng)制執(zhí)行前置條件、后置條件以及代碼不變量,最好使用顯式Python條件表達(dá)式。

3. 使用生成器和Yield語(yǔ)句進(jìn)行延遲加載

考慮這樣一個(gè)場(chǎng)景:您擁有一個(gè)大型文檔數(shù)據(jù)集。您需要處理文檔,并對(duì)每個(gè)文檔執(zhí)行某些操作。然而由于文件太大,您無(wú)法同時(shí)將所有文檔加載到內(nèi)存中并對(duì)它們進(jìn)行預(yù)處理。

一種可行的解決方案是只在需要時(shí)將文檔加載到內(nèi)存中,并且一次只處理一個(gè)文檔,這也稱為延遲加載。即使我們知道需要什么文檔,也不會(huì)加載資源,除非有需要。當(dāng)我們的代碼中沒(méi)有使用大量文檔時(shí),不需要在內(nèi)存中保留它們。這正是生成器和yield語(yǔ)句解決這個(gè)問(wèn)題的方法。

生成器允許延遲加載,從而提高Python代碼執(zhí)行的內(nèi)存效率。值可以根據(jù)需要?jiǎng)討B(tài)生成,減少了內(nèi)存占用資源,并提高了執(zhí)行速度。

import os
def load_documents(directory):
	for document_path in os.listdir(directory):
    	    with open(document_path) as _file:
        	        yield _file
def preprocess_document(document):
		filtered_document = None
		# preprocessing code for the document stored in filtered_document
		return filtered_document
directory = "docs/"
for doc in load_documents(directory):
		   preprocess_document(doc)

在上面的函數(shù)中,load_documents函數(shù)使用yield關(guān)鍵字。該方法返回類型<class generator>的對(duì)象。當(dāng)我們迭代處理這個(gè)對(duì)象時(shí),它從最后一個(gè)yield語(yǔ)句所在的位置繼續(xù)執(zhí)行。因此,加載和處理單個(gè)文檔,提高了Python代碼的效率。

4. 使用上下文管理器防止內(nèi)存泄漏

對(duì)任何語(yǔ)言來(lái)說(shuō),有效地利用資源最重要。我們只在需要時(shí)才通過(guò)使用生成器在內(nèi)存中加載一些內(nèi)容,如上所述。然而,當(dāng)我們的程序不再需要某個(gè)資源時(shí),關(guān)閉該資源同樣重要。我們需要防止內(nèi)存泄漏,并執(zhí)行適當(dāng)?shù)馁Y源拆卸以節(jié)省內(nèi)存。

上下文管理器簡(jiǎn)化了資源設(shè)置和拆卸的常見(jiàn)用例。資源不再需要時(shí),釋放它們很重要,即使在出現(xiàn)異常和失敗的情況下也是如此。上下文管理器使用自動(dòng)清理,同時(shí)保持代碼簡(jiǎn)潔易讀,降低內(nèi)存泄漏的風(fēng)險(xiǎn)。

資源可以有多種變體,比如數(shù)據(jù)庫(kù)連接、鎖、線程、網(wǎng)絡(luò)連接、內(nèi)存訪問(wèn)和文件句柄。不妨關(guān)注最簡(jiǎn)單的情況:文件句柄。這里的挑戰(zhàn)是確保每個(gè)打開(kāi)的文件只關(guān)閉一次。關(guān)閉文件失敗可能導(dǎo)致內(nèi)存泄漏,而試圖關(guān)閉文件句柄兩次會(huì)導(dǎo)致運(yùn)行時(shí)錯(cuò)誤。為了解決這個(gè)問(wèn)題,應(yīng)該將文件句柄封裝在try-except-finally塊中。這確保了文件被正確關(guān)閉,不管執(zhí)行過(guò)程中是否發(fā)生了錯(cuò)誤。下面是具體實(shí)現(xiàn)的樣子:

file_path = "example.txt"
file = None
try:
	file = open(file_path, 'r')
	contents = file.read()
	print("File contents:", contents)
finally:
	if file is not None:
    	file.close()

然而,Python提供了一個(gè)使用上下文管理器的更優(yōu)雅的解決方案,它自動(dòng)處理資源管理。下面介紹我們?nèi)绾问褂梦募舷挛墓芾砥骱?jiǎn)化上述代碼:

file_path = "example.txt"
with open(file_path, 'r') as file:
	contents = file.read()
	print("File contents:", contents)

在這個(gè)版本中,我們不需要顯式關(guān)閉文件。上下文管理器負(fù)責(zé)處理它,防止?jié)撛诘膬?nèi)存泄漏。

雖然Python為文件處理提供了內(nèi)置的上下文管理器,但我們也可以為自定義類和函數(shù)創(chuàng)建自己的上下文管理器。針對(duì)基于類的實(shí)現(xiàn),我們定義了__enter__和__exit__dunder方法。這里有一個(gè)基本的例子:

class CustomContextManger:
	def __enter__(self):
    	    # Code to create instance of resource
    	    return self
	def __exit__(self, exc_type, exc_value, traceback):
    	    # Teardown code to close resource
     	    return None

現(xiàn)在,我們可以在“with ”塊中使用這個(gè)自定義的上下文管理器:

with CustomContextManger() as _cm:
	print("Custom Context Manager Resource can be accessed here")

這種方法保持了上下文管理器簡(jiǎn)潔明了的語(yǔ)法,同時(shí)允許我們根據(jù)需要處理資源。

5. 用裝飾器分離關(guān)注點(diǎn)

我們經(jīng)??吹斤@式地實(shí)現(xiàn)具有相同邏輯的多個(gè)函數(shù)。這是普遍存在的代碼風(fēng)格,過(guò)多的代碼重復(fù)會(huì)使代碼難以維護(hù)和不可擴(kuò)展。裝飾器用于將類似的功能封裝在一個(gè)地方。當(dāng)一個(gè)相似的功能被多個(gè)其他函數(shù)使用時(shí),我們可以通過(guò)在裝飾器中實(shí)現(xiàn)通用功能來(lái)減少代碼重復(fù)。它遵循面向方面的編程(AOP)和單一職責(zé)原則。

裝飾器在Django、Flask和FastAPI等Python Web框架中被大量使用。不妨通過(guò)在Python中將解釋器用作日志記錄的中間件來(lái)解釋裝飾器的效果。在生產(chǎn)環(huán)境中,我們需要知道服務(wù)一個(gè)請(qǐng)求需要多長(zhǎng)時(shí)間。這是一個(gè)常見(jiàn)的用例,將在所有端點(diǎn)之間共享。因此,不妨實(shí)現(xiàn)一個(gè)簡(jiǎn)單的基于裝飾器的中間件,它將記錄服務(wù)請(qǐng)求所花費(fèi)的時(shí)間。

下面的虛擬函數(shù)用于服務(wù)用戶請(qǐng)求。

def service_request():
	# Function body representing complex computation
	return True

現(xiàn)在,我們需要記錄這個(gè)函數(shù)執(zhí)行所花費(fèi)的時(shí)間。一種方法是在這個(gè)函數(shù)中添加日志記錄,如下所示:

import time
def service_request():
	start_time = time.time()
	# Function body representing complex computation
	print(f"Time Taken: {time.time() - start_time}s")
	return True

雖然這種方法有效,但它會(huì)導(dǎo)致代碼重復(fù)。如果我們添加更多的路由,將不得不在每個(gè)函數(shù)中重復(fù)日志代碼。這增加了代碼重復(fù),因?yàn)檫@種共享日志功能需要添加到每個(gè)實(shí)現(xiàn)中。我們使用裝飾器進(jìn)行移除。

日志中間件將按以下方式來(lái)實(shí)現(xiàn):

def request_logger(func):
	def wrapper(*args, **kwargs):
    	    start_time = time.time()
    	    res = func()
    	    print(f"Time Taken: {time.time() - start_time}s")
    	    return res
	return wrapper

在這個(gè)實(shí)現(xiàn)中,外部函數(shù)是裝飾器,它接受函數(shù)作為輸入。內(nèi)部函數(shù)實(shí)現(xiàn)日志功能,而輸入函數(shù)在包裝器中被調(diào)用。

現(xiàn)在,我們只需用request_logger裝飾器裝飾原來(lái)的service_request函數(shù):

@request_logger
def service_request():
	# Function body representing complex computation
	return True

使用@符號(hào)將service_request函數(shù)傳遞給request_logger裝飾器。它記錄所花費(fèi)的時(shí)間,并在不修改代碼的情況下調(diào)用原始函數(shù)。這種關(guān)注點(diǎn)分離讓我們得以以類似的方式輕松地將日志記錄添加到其他服務(wù)方法中,如下所示:

@request_logger
def service_request():
	# Function body representing complex computation
	return True
@request_logger
def service_another_request():
	# Function body
	return True

6. 匹配Case語(yǔ)句

匹配語(yǔ)句是在Python3.10中引入的,所以它是Python語(yǔ)法的一個(gè)相當(dāng)新的添加。它允許更簡(jiǎn)單、更易讀的模式匹配,避免了典型if- if-else語(yǔ)句中過(guò)多的樣板文件和分支。

針對(duì)模式匹配,匹配case語(yǔ)句是更自然的編寫(xiě)方式,因?yàn)樗鼈儾灰欢ㄐ枰駰l件語(yǔ)句中那樣返回布爾值。來(lái)自Python文檔中的以下示例展示了匹配case語(yǔ)句聲明如何比條件語(yǔ)句更具靈活性。

def make_point_3d(pt):
	match pt:
    	    case (x, y):
        		return Point3d(x, y, 0)
    	    case (x, y, z):
        		return Point3d(x, y, z)
    	    case Point2d(x, y):
        		return Point3d(x, y, 0)
    	    case Point3d(_, _, _):
        		return pt
    	    case _:
        		raise TypeError("not a point we support")

根據(jù)文檔,如果沒(méi)有模式匹配,這個(gè)函數(shù)的實(shí)現(xiàn)將需要幾次isinstance()檢查、一兩個(gè)len()調(diào)用以及一個(gè)更復(fù)雜的控制流。揭開(kāi)底層,匹配示例和傳統(tǒng)Python版本轉(zhuǎn)換成相似的代碼。然而,熟悉模式匹配后,可能會(huì)首選匹配case方法,因?yàn)樗峁┝烁逦?、更自然的語(yǔ)法。

總的來(lái)說(shuō),匹配case語(yǔ)句為模式匹配提供了一種經(jīng)過(guò)改進(jìn)的替代方案,這可能會(huì)在較新的代碼庫(kù)中變得更加普遍。

7. 外部配置文件

在生產(chǎn)環(huán)境中,我們的大部分代碼依賴外部配置參數(shù),比如API密鑰、密碼和各種設(shè)置。出于可擴(kuò)展性和安全性的考慮,將這些值直接硬編碼到代碼中被認(rèn)為是糟糕的做法。相反,將配置與代碼本身分開(kāi)來(lái)至關(guān)重要。我們通常使用JSON或YAML等配置文件來(lái)存儲(chǔ)這些參數(shù),確保它們易于訪問(wèn)代碼,無(wú)需直接嵌入到其中。

一種日常的用例是實(shí)現(xiàn)多個(gè)連接參數(shù)的數(shù)據(jù)庫(kù)連接。

我們可以將這些參數(shù)保留在一個(gè)單獨(dú)的YAML文件中。

# config.yaml
database:
  host: localhost
  port: 5432
  username: myuser
  password: mypassword
  dbname: mydatabase

為了處理這個(gè)配置,我們定義了一個(gè)名為DatabaseConfig的類:

class DatabaseConfig:
	def __init__(self, host, port, username, password, dbname):
    	    self.host = host
    	    self.port = port
    	    self.username = username
    	    self.password = password
    	    self.dbname = dbname
	@classmethod
	def from_dict(cls, config_dict):
    	    return cls(**config_dict)

在這里,from_dict類方法充當(dāng)DatabaseConfig類的構(gòu)建器方法,允許我們從字典創(chuàng)建數(shù)據(jù)庫(kù)配置實(shí)例。

在我們的主代碼中,我們可以使用參數(shù)hydration和構(gòu)建器方法來(lái)創(chuàng)建數(shù)據(jù)庫(kù)配置。通過(guò)讀取外部YAML文件,我們提取數(shù)據(jù)庫(kù)字典,并使用它為配置類創(chuàng)建實(shí)例:

import yaml
def load_config(filename):
	with open(filename, "r") as file:
    	return yaml.safe_load(file)
config = load_config("config.yaml")
db_config = DatabaseConfig.from_dict(config["database"])

這種方法不需要將數(shù)據(jù)庫(kù)配置參數(shù)直接硬編碼到代碼中。它還比使用參數(shù)解析器有所改進(jìn),因?yàn)槲覀儾辉傩枰诿看芜\(yùn)行代碼時(shí)傳遞多個(gè)參數(shù)。此外,通過(guò)參數(shù)解析器訪問(wèn)配置文件路徑,我們可以確保代碼保持靈活性,而不依賴硬編碼路徑。這種方法便于更容易管理配置參數(shù),可以隨時(shí)修改配置參數(shù),不需要更改代碼庫(kù)。

結(jié)束語(yǔ)

我們?cè)诒疚闹杏懻摿藰I(yè)界用于生產(chǎn)就緒代碼的一些最佳實(shí)踐。這些都是常見(jiàn)的行業(yè)實(shí)踐,可以緩解人們?cè)趯?shí)際情形中可能面臨的多個(gè)問(wèn)題。

值得一提的是,盡管有所有這些最佳實(shí)踐,文檔、文檔字符串和測(cè)試驅(qū)動(dòng)開(kāi)發(fā)是迄今為止最重要的實(shí)踐。重要的是要考慮一個(gè)函數(shù)應(yīng)該做什么,然后將所有的設(shè)計(jì)決策和實(shí)現(xiàn)記入文檔,因?yàn)殡S著時(shí)間的推移,人們不斷更改代碼庫(kù)。

原文標(biāo)題: Mastering Python: 7 Strategies for Writing Clear, Organized, and Efficient Code,作者:Kanwal Mehreen

鏈接:https://www.kdnuggets.com/mastering-python-7-strategies-for-writing-clear-organized-and-efficient-code

責(zé)任編輯:武曉燕 來(lái)源: 51CTO
相關(guān)推薦

2024-06-28 09:39:58

2024-11-06 14:26:40

2024-10-10 15:24:50

JSONPython

2017-09-14 12:45:35

2009-02-16 16:49:53

DBA經(jīng)驗(yàn)

2022-04-14 10:40:11

領(lǐng)導(dǎo)者IT團(tuán)隊(duì)遠(yuǎn)程團(tuán)隊(duì)

2023-03-27 15:05:10

Python技巧

2010-09-01 09:39:07

CSS

2022-06-07 09:30:35

JavaScript變量名參數(shù)

2021-10-18 13:26:15

大數(shù)據(jù)數(shù)據(jù)分析技術(shù)

2020-03-23 10:59:52

CISO網(wǎng)絡(luò)安全漏洞

2021-02-23 10:48:30

Python代碼開(kāi)發(fā)

2023-11-28 12:07:06

Python代碼

2020-07-15 14:51:39

代碼C+開(kāi)發(fā)

2022-07-18 10:15:16

Python

2010-09-09 17:06:12

CSS

2020-08-27 07:00:00

代碼軟件應(yīng)用程序

2025-04-03 08:25:26

2022-05-25 10:35:21

資產(chǎn)管理者SAM

2016-12-13 10:06:25

編寫(xiě)Java單元測(cè)試技巧
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)