編寫高質量代碼--改善python程序的建議(三)
eval("1+1==2") #進行判斷 eval("'A'+'B'") #字符連接 eval("1+2") #數字相加
python中eval()函數將字符串str當成有效的表達式來求值并返回計算結果,其函數聲明如下:
eval(expression[, globals[,locals]]) #globals為字典形式,表示全局命名空間 #locals為任何映射對象,表示局部命名空間
“eval is evil”,這時一句廣為人知的對eval的評價,它主要針對的是eval()的安全性。假設web頁面調用eval來根據用戶的輸入,計算python表達式的值,由于網絡環境下運行它的用戶并非都是可信任的,eval()可以將任何字符串當作表達式求值,這也就意味著有空子可鉆,如果用戶輸入下面代碼,就會得到當前目錄下的所有文件列表。
__import__("os").system("dir")
如果有人想搞破壞,他輸入了如下字符串(禁止嘗試),則會刪掉當前目錄下的所有文件
__import__("os").system("del * /Q")
當然有人說可以在globals參數中禁止全局命名空間的訪問,比如將運算范圍限定為幾個常用的數學函數:
math_fun_list = ['acos', 'asin', 'atan', 'pi'] math_fun_dict = dict([(k, globals().get(k) for k in math_fun_list)]) eval(string, {"__builtins__":None}, math_fun_dict)
這樣確實解決了安全問題,但是試試輸入以下字符:
[c for c in ().__class__.__bases__[0].__subclasses__() if c.__name__ == 'Quitter'][0](0)() # ().__class__.__bases__[0].__subclasses__()顯示object類的所有子類,類Quitter與“quite”功能綁定,因此上面的輸入會直接導致程序退出。
因此在實際應用過程中,如果使用對象不是信任源,應該盡量避免使用eval,在需要使用eval的地方可以用安全性更好的ast.literal_eval替代。
建議十四:使用enumerate()獲取序列迭代的索引和值
函數enumerate()主要是為了解決在循環中獲取索引以及對應值的問題,它具有一定的惰性,每次僅在需要的時候才會產生一個(index,item)對。使用如下:
enumerate(sequence, start=0) #sequence可以是list、 set等任何迭代對象 #默認從0開始 #函數返回一個迭代器,可以使用next()方法獲取下一個元素
建議十五:分清 == 與 is 的使用場景
is 表示的是對象標識符(object identity),即比較兩個對象在內存中是否擁有同一塊內存空間,而 == 表示的意思是相等(equal),用來檢驗兩個對象的值是否相等。
還有需要注意的是,在python中有string interning(字符串駐留)機制:對于較小的字符串,為了提高系統性能會保留其值的一個副本,當創建新的字符串的時候直接指向該副本即可。而長字符串不會駐留。
建議十六:考慮兼容性,盡可能使用Unicode
python內建的字符串有兩種類型:str和Unicode,它們擁有共同的祖先basestring。
Unicode也稱做萬國碼,它為每種語言設定了唯一的二進制編碼表示方式,提供從數字代碼到不同語言字符集之間的映射,從而可以滿足跨平臺、夸語言之間的文本處理要求。
Unicode編碼系統可以分為編碼方式和實現方式兩個層面,在編碼方式上,分為UCS-2和UCS-4兩種方式,UCS-2用兩個字節編碼,UCS-4用4個字節編碼。一個字符的Unicode編碼是確定的,但是在實際傳輸過程中,由于系統平臺的不同以及處于節省空間的目的,實現方式有所差異。Unicode的實現方式稱為Unicode轉換格式,簡稱為UTF,包括UTF-7、UTF-16、UTF-32、UTF-8等,較為常見的是UTF-8.他的特點是對不同范圍的字符使用不同長度的編碼,其中0x00 ~ 0x7F的字符的UTF-8編碼與ASCII編碼完全相同,UTF-8編碼的最大長度是4個字節。
通常用python處理中文字符經常會遇見一下幾個問題。
-
讀出文件的內容顯示為亂碼
fp = open("test.txt","r") print fp.read() fp.close()
問題:讀入的文件test.txt用UTF-8編碼形式保存,但是Windows的本地默認編碼是CP936,在Windows系統中它被映射為GBK編碼,所以直接顯示UTF-8字符的時候,不兼容。解決辦法:首先對讀入的字符用UTF-8進行解碼,然后再用GBK進行編碼。
fp = open("test.txt","r") print (fp.read().decode("utf-8")).encode("gbk")
decode()方法講其他編碼對應的字符串解碼為Unicode,而encode()方法將Unicode編碼轉換為另一種編碼。另外:上面的例子在某些情況下(如test.txt使用Notepad軟件以UTF-8編碼形式保存)可能還會出現異常,這是因為有些軟件在保存UTF-8編碼的時候,會在文件最開始的地方插入不可見的字符BOM,利用codecs模塊可以方便地處理這種問題
import codecs fp = open("test.txt",'r') content = fp.read() fp.close() if content[:3] == codecs.BOM_UTF8: content = content[3:] print content.decode("utf-8")
-
當python源文件中包含中文字符的時候拋出SyntaxError異常python中默認的編碼是ASCII編碼,為了讓接收器知道如何正確處理字符,需要在源文件中進行編碼聲明,聲明可以用正則表達式表示
"coding[:=]\s*([-\w.]+)"
一般來說進行源文件編碼聲明有三種方式:
#coding=<encoding name> #!/usr/bin/python # -*- coding: <encoding name> -*- #!/usr/bin/python # vim: set fileencoding=<encoding name>:
-
普通字符和Unicode進行字符串連接的時候拋出UnicodeDecodeError異常。
# coding=utf-8 s = "中文" + u"Chinese Test" print s
使用 +操作符來進行字符串的連接時,左邊為中文字符串,類型為str,右邊為Unicode字符串,當兩種類型的字符串連接的時候,python將左邊的中文字符串轉換為Unicode再與右邊的Unicode字符串連接,將str轉換為Unicode時使用系統默認的ASCII編碼對字符串進行編碼,就會出現UnicodeDecodeError異常。解法辦法:
-
指定str轉換為Unicode時的編碼方式。
#coding=utf-8 s = "中文".decode('gbk') + u"Chinese Test"
-
將Unicode字符串進行UTF-8編碼
s = "中文" + u"Chinese Test".encode("utf-8")
建議十七:構建合理的包層次來管理module
本質上每一個python文件都是一個模塊,使用模塊可以增強代碼的可維護性和可重用性。我們需要包(Package)來合理的組織項目的層次來管理模塊。
包即目錄,但是與普通目錄不同,它除了包含常規的python文件(模塊)以外,還包含一個__init__.py文件,同時它允許嵌套。包結構如下:
Package/ __init__.py Module1.py Molude2.py Subpackage/ __init__.py Module1.py Module2.py
包中的模塊可以通過 . 訪問符進行訪問,即 包名.模塊名 。如上述嵌套結構中訪問Package下的Module1可以使用 Package.Module1 ,而訪問Subpackage中的Module1則可以使用 Package.Subpackage.Module1
包中的模塊同樣可以被導入其他模塊中,有以下幾種方法:
-
直接導入一個包
import Package
-
導入子模塊或者子包
from Package import Module1 import Package.Module1 from Package import Subpackage import Package.Subpackage from Package.Subpackage import Module1 import Package.Subpackage.Module1
前面提到包的目錄下應該有 init .py 文件,它除了區分包和普通目錄,還可以在該文件中申明模塊級別的import語句從而使其編程包級別可見。舉例來看:
上例中如果要import包Package下Module1中的類Test,當__init__.py為空的時候需要使用完整路徑
from Package.Module1 import Test
但是如果在__init__.py中添加from Module1 import Test語句,則可以直接使用下面語句來導入類Test
from Package import Test
注意:如果__init__.py為空,當意圖使用 from Package import *將包Package中所有的模塊導入當前名字空間時并不可以,這是因為不同平臺間的命名規則不同,python解釋器不能正確判定模塊在對應平臺如何導入,因此它僅僅執行__init__.py文件,如果要控制模塊的導入,則需要修改__init__.py
__all__ = ['Module1', 'Module2', 'Subpackage']
這樣就可以了。
包的使用可以帶來如下便利:
-
合理組織代碼,易于維護和使用。以下是一個可供參考的python項目結構:
ProjectName/ |---README |----LICENSE |----setup.py |-----requirements.txt |------sample/ | |----__init__.py | |----core.py | |----helpers.py |------docs/ | |------conf.py | |------index.rst |------bin/ |------package/ | |-----__init__.py | |-----subpackage/ | |-----...... |------tests/ | |------test_basic.py | |------test_advancde.py
-
能夠有效地避免名稱空間沖突。
總結:至此羅列了python一些慣用法,掌握和熟練使用這些是非常必要的,后面會接著說一些基礎語法需要注意的地方。
參考:編寫高質量代碼--改善python程序的91個建議