編寫高質量代碼--改善python程序的建議(三)

jopen 8年前發布 | 18K 次閱讀 Python Unicode Python開發

原文發表在我的 博客主頁 ,轉載請注明出處!

建議十三:警惕eval()的安全漏洞

相信經常處理文本數據的同學對eval()一定是欲罷不能,他的使用非常簡單:

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異常。解法辦法:

  1. 指定str轉換為Unicode時的編碼方式。

    #coding=utf-8
    s = "中文".decode('gbk') + u"Chinese Test"
  2. 將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

    包中的模塊同樣可以被導入其他模塊中,有以下幾種方法:

  3. 直接導入一個包

    import Package
  4. 導入子模塊或者子包

    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個建議

來自: http://www.cnblogs.com/cotyb/p/5091318.html

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!