原文發表在個人博客主頁,轉載請註明出處!python
建議二十八:區別對待可變對象和不可變對象
python中一切皆對象,每個對象都有一個惟一的標識符(id())、類型(type())以及值,對象根據其值可否修改分爲可變對象和不可變對象,其中數字、字符串、元組屬於不可變對象,字典以及列表、字節數組屬於可變對象。
來看一段程序:數組
class Student(object): def __init__(self,name,course=[]): self.name = name self.course = course def addcourse(self,coursename): self.course.append(coursename) def printcourse(self): for item in self.course: print item xl = Student('xl') xl.addcourse('computer') xl.addcourse('automation') print xl.name + "'s course:" xl.printcourse() print "~~~~~~~~~~~~~~~~~~~~~~~~~~~~" cyj = Student('cyj') cyj.addcourse('software') cyj.addcourse('NLP') print cyj.name + "'s course:" cyj.printcourse()
運行結果會讓初學者大吃一驚:數據結構
xl's course: computer automation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ cyj's course: computer automation software NLP
經過查看xl和cyj的course變量的id,發現他們的值是同樣的,即指向內存中的同一塊地址,可是xl和cyj倒是兩個不一樣的對象。在實例化這兩個對象的時候,這兩個對象被分配了不一樣的內存空間,而且調用init()函數進行初始化,但因爲init()函數的第二個參數是個默認參數,默認參數在函數調用的時候僅僅被評估一次,之後都會使用第一次評估的結果,所以實際上對象空間裏面course所指向的是list的地址,這時咱們在將可變對象做爲默認參數的時候要警戒的,對可變對象的更改會直接影響原對象,能夠用以下方式解決:app
def __init__(self,name,course=None): self.name = name if course is None:course = [] self.course = course
對於不可變對象來講,當咱們對其進行相關操做的時候,python實際上仍然保存原來的值,從新建立一個新的對象。當有兩個對象同時指向一個字符串對象的時候,對其中一個對象的操做並不會影響另外一個對象。好比:函數
str1 = "write pythonic code" str2 = str1 str1 = str1[-4:] print id(str1) print id(str2) print str1 print str2
建議二十九:和{}:一致性容器初始化形式
列表是一個頗有用的數據結構,因爲其靈活性在實際應用中被普遍使用。對於列表來講,列表解析十分經常使用。
列表解析的語法以下,它迭代iterable中的每個元素,當條件知足的時候便根據表達式expr計算的內容生成一個元素並放入新的列表中,依次類推,最終返回整個列表。大數據
[expr for iter_item in iterable if cond_expr]
列表解析的使用很是靈活:ui
nested_list = [['Hello', 'World'],['Goodbye', 'World']] nested_list = [[ele.upper() for ele in word] for word in nested_list]
[(a,b) for a in ['1', '2', '3', '4'] for b in ['a', 'b', 'c', 'd'] if a != b]
def f(v): if v%2 == 0: v = v ** 2 else: v = v + 1 return v print [f(v) for v in [1,2,3,-1] if v > 0] print [v ** 2 if v %2 == 0 else v + 1 for v in [1,2,3,-1] if v > 0]
fp = open('wdf.py','r') res = [i for i in fp if 'weixin' in i] print res
爲何要推薦在須要生成列表的時候使用列表解析呢?debug
除了列表可使用列表解析的語法以外,其餘內置的數據結構也支持,以下:code
#generator (expr for iter_item in iterable if cond_expr) #set {expr for iter_item in iterable if cond_expr} #dict {expr1: expr2 for iter_item in iterable if cond_expr}
建議三十:記住函數傳參既不是傳值也不是傳引用
以往關於python中函數傳參數有三種觀點:orm
這些理解都是有些誤差的,python中的賦值與咱們所理解的C/C++等語言的賦值的意思並不同。以以下語句爲例來看C/C++和python是如何運做的
a = 5, b= a, b = 7
C/C++中當執行b=a的時候,在內存中申請一塊內存並將a的值複製到該內存中,當執行b=7以後是將b對應的值從5修改到7
python中賦值並非複製,b=a操做使得b與a引用同一對象,而b=7則是將b指向對象7
所以,對於python函數參數既不是傳值也不是傳引用,應該是傳對象或者說傳對象的引用。函數參數在傳遞的過程當中將整個對象傳入,對可變對象的修改在函數外部以及內部均可見,調用者和被調用者之間共享這個對象,而對於不可變對象,因爲並不能真正被修改,所以,修改每每是經過生成一個新對象而後賦值來實現的。
建議三十一:慎用變長參數
python支持可變長度的參數列表,能夠經過在函數定義的時候使用*args和**kwargs這兩個特殊語法來實現。
使用args來實現可變參數列表:args用於接收一個包裝爲元組形式的參數列表來傳遞非關鍵字參數,參數個數能夠任意:
def sumf(*args): res = 0 for x in args[:]: res += x return res print sumf(2,3,4) print sumf(1,2,3,4,5)
使用**kwargs接收字典形式的關鍵字參數列表,其中字典的鍵值分別表示不可變參數的參數名和值:
def category_table(**kwargs): for name, value in kwargs.items(): print '{0} is a kind of {1}'.format(name, value) category_table(apple = 'fruit', carrot = 'vegetable')
當普通參數,默認參數,和上述兩種參數同時存在的時候,會優先給普通參數和默認參數賦值,爲何要慎用可變長參數呢?
參數三十二:深刻理解str()和repr()的區別
這兩個方法均可以將python中的對象轉換爲字符串,他們的使用以及輸出都很是類似,區別呢?
obj == eval(repr(obj))
建議三十三:分清staticmethod和classmethod的使用場景
python中的靜態方法(staticmethod)和類方法(classmethod)都依賴於裝飾器來實現,用法以下:
#staticmethod class C(object): @staticmethod def f(arg1, arg2, ...): #classmethod class C(object): @classmethod def f(arg1, arg2, ...):
靜態方法和類方法均可以經過類名.方法名或者實例.方法名的形式來訪問。其中靜態方法沒有常規方法的特殊行爲,如綁定、非綁定、隱式參數等規則,而類方法的調用使用類自己做爲其隱含參數,但調用自己並不須要顯示提供該參數。
那爲何須要靜態方法和類方法呢?假設有水果類Fruit,它用屬性total表示總量,用set()來設置重量,print_total()方法來打印水果數量。類Apple和類Orange繼承自Fruit,現須要分別跟蹤不一樣類型的水果的總量,實現方法彙總:
class Fruit(object): total = 0 def print_total(cls): print cls.total @classmethod def set(cls, value): cls.total = value class Apple(Fruit): pass class Orange(Fruit): pass app1 = Apple() app1.set(200) app2 = Apple() org1 = Orange() org1.set(300) org2 = Orange() app1.print_total() org1.print_total()
簡單分析可知,針對不一樣種類的水果對象調用set()方法的時候隱形傳入的參數爲該對象所對應的類,在調用set()的過程當中動態生成了對應的類的類變量。
靜態方法通常適用於既不跟特定的實例相關也不跟特定的類相關的方法。他存在於類中,較以外部函數可以更加有效的將代碼組織起來,從而使相關代碼的垂直距離更近,提升代碼的可維護性。
參考:編寫高質量代碼--改善python程序的91個建議