59個Python使用技巧,從此你的Python與眾不同(二)
- 2020 年 2 月 19 日
- 筆記
11. Python代碼微優化之加快查找
collections.OrderedDict類:
def __setitem__(self, key, value, dict_setitem=dict.__setitem__): if key not in self: root = self.__root last = root[0] last[1] = root[0] = self.__map[key] = [last, root, key] return dict_setitem(self, key, value)
注意最後一個參數:dict_setitem=dict.setitem。如果你仔細想就會感覺有道理。將值關聯到鍵上,你只需要給__setitem__傳遞三個參數:要設置的鍵,與鍵關聯的值,傳遞給內建dict類的__setitem__類方法。等會,好吧,也許最後一個參數沒什麼意義。最後一個參數其實是將一個函數綁定到局部作用域中的一個函數上。具體是通過將dict.__setitem__賦值為參數的默認值。這裡還有另一個例子:
def not_list_or_dict(value): return not (isinstance(value, dict) or isinstance(value, list)) def not_list_or_dict(value, _isinstance=isinstance, _dict=dict, _list=list): return not (_isinstance(value, _dict) or _isinstance(value, _list))
這裡我們做同樣的事情,把本來將會在內建命名空間中的對象綁定到局部作用域中去。因此,python將會使用LOCAL_FAST而不是LOAD_GLOBAL(全局查找)。那麼這到底有多快呢?我們做個簡單的測試:
$ python -m timeit -s 'def not_list_or_dict(value): return not (isinstance(value, dict) or isinstance(value, list))' 'not_list_or_dict(50)' 1000000 loops, best of 3: 0.48 usec per loop $ python -m timeit -s 'def not_list_or_dict(value, _isinstance=isinstance, _dict=dict, _list=list): return not (_isinstance(value, _dict) or _isinstance(value, _list))' 'not_list_or_dict(50)' 1000000 loops, best of 3: 0.423 usec per loop
換句話說,大概有11.9%的提升 [2]。比我在文章開始處承諾的5%還多!
12. 包管理
Python世界最棒的地方之一,就是大量的第三方程序包。同樣,管理這些包也非常容易。按照慣例,會在 requirements.txt 文件中列出項目所需要的包。每個包佔一行,通常還包含版本號。
pelican==3.3 Markdown pelican-extended-sitemap==1.0.0
13. Python函數參數默認值的陷阱和原理深究
Python 2.7.9 (default, Dec 19 2014, 06:05:48) [GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.56)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> def generate_new_list_with(my_list=[], element=None): ... my_list.append(element) ... return my_list ... >>> list_1 = generate_new_list_with(element=1) >>> list_1 [1] >>> list_2 = generate_new_list_with(element=2) >>> list_2 [1, 2] >>>
可見代碼運行結果並不和我們預期的一樣。list_2在函數的第二次調用時並沒有得到一個新的list並填入2,而是在第一次調用結果的基礎上append了一個2。為什麼會發生這樣在其他編程語言中簡直就是設計bug一樣的問題呢?
可見如果參數默認值是在函數編譯compile階段就已經被確定。之後所有的函數調用時,如果參數不顯示的給予賦值,那麼所謂的參數默認值不過是一個指向那個在compile階段就已經存在的對象的指針。如果調用函數時,沒有顯示指定傳入參數值得話。那麼所有這種情況下的該參數都會作為編譯時創建的那個對象的一種別名存在。
如果參數的默認值是一個不可變(Imuttable)數值,那麼在函數體內如果修改了該參數,那麼參數就會重新指向另一個新的不可變值。而如果參數默認值是和本文最開始的舉例一樣,是一個可變對象(Muttable),那麼情況就比較糟糕了。所有函數體內對於該參數的修改,實際上都是對compile階段就已經確定的那個對象的修改。
14. 單下劃線(_)
1. 在解釋器中:在這種情況下,「_」代表交互式解釋器會話中上一條執行的語句的結果。這種用法首先被標準CPython解釋器採用,然後其他類型的解釋器也先後採用。
>>> _ Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name '_' is not defined >>> 42 >>> _ 42 >>> 'alright!' if _ else ':(' 'alright!' >>> _ 'alright!'
2. 作為一個名稱:這與上面一點稍微有些聯繫,此時「 」作為臨時性的名稱使用。這樣,當其他人閱讀你的代碼時將會知道,你分配了一個特定的名稱,但是並不會在後面再次用到該名稱。例如,下面的例子中,你可能對循環計數中的實際值並不感興趣,此時就可以使用「 」。
n = 42 for _ in range(n): do_something()
3. 國際化:也許你也曾看到」_「會被作為一個函數來使用。這種情況下,它通常用於實現國際化和本地化字符串之間翻譯查找的函數名稱,這似乎源自並遵循相應的C約定。例如,在Django文檔「轉換」章節中,你將能看到如下代碼:
from django.utils.translation import ugettext as _ from django.http import HttpResponse def my_view(request): output = _("Welcome to my site.") return HttpResponse(output)
可以發現,場景二和場景三中的使用方法可能會相互衝突,所以我們需要避免在使用「」作為國際化查找轉換功能的代碼塊中同時使用「」作為臨時名稱。
15. 名稱前的單下劃線(如:_shahriar)
程序員使用名稱前的單下劃線,用於指定該名稱屬性為「私有」。這有點類似於慣例,為了使其他人(或你自己)使用這些代碼時將會知道以「_」開頭的名稱只供內部使用。正如Python文檔中所述:
以下劃線 __ 為前綴的名稱(如_pam)應該被視為API中非公開的部分(不管是函數、方法還是數據成員)。此時,應該將它們看作是一種實現細節,在修改它們時無需對外部通知。
正如上面所說,這確實類似一種慣例,因為它對解釋器來說確實有一定的意義,如果你寫了代碼 : from <模塊/包名> import * ,那麼以 _ 開頭的名稱都不會被導入,除非模塊或包中的 __all__ 列表顯式地包含了它們。了解更多請查看 Importing * in Python
16. 名稱前的雙下劃線(如:__shahriar)
名稱(具體為一個方法名)前雙下劃線 _ 的用法並不是一種慣例,對解釋器來說它有特定的意義。Python中的這種用法是為了避免與子類定義的名稱衝突。Python文檔指出,__spam 這種形式(至少兩個前導下劃線,最多一個後續下劃線)的任何標識符將會被 正如所預料的,「_internal_use」並未改變,而「__method_name」卻被變成了「_ClassName__method_name」。此時,如果你創建A的一個子類B,那麼你將不能輕易地覆寫A中的方法「__method_name」。spam 這種形式原文取代,在這裡 classname 是去掉前導下劃線的當前類名。例如下面的例子:
>>> class A(object): ... def _internal_use(self): ... pass ... def __method_name(self): ... pass ... >>> dir(A()) ['_A__method_name', ..., '_internal_use']
正如所預料的,「_internal_use」並未改變,而「__method_name」卻被變成了「_ClassName__method_name」。此時,如果你創建A的一個子類B,那麼你將不能輕易地覆寫A中的方法「__method_name」。
17. 名稱前後的雙下劃線(如:init)
這種用法表示Python中特殊的方法名。其實,這只是一種慣例,對Python系統來說,這將確保不會與用戶自定義的名稱衝突。通常,你將會覆寫這些方法,並在裏面實現你所需要的功能,以便Python調用它們。例如,當定義一個類時,你經常會覆寫「init」方法。
雖然你也可以編寫自己的特殊方法名,但不要這樣做。
18. 隱藏特性 1,函數unpack
def foo(x, y): print x, y alist = [1, 2] adict = {'x': 1, 'y': 2} foo(*alist) # 1, 2 foo(**adict) # 1, 2
19. 隱藏特性 2, 鏈式比較操作符
>>> x = 3 >>> 1 < x < 5 True >>> 4 > x >=3 True
20. 隱藏特性 3,函數的默認參數
>>> def foo(x=[]): ... x.append(1) ... print x ... >>> foo() [1] >>> foo() [1, 1]
更安全的做法是:
>>> def foo(x=None): ... if x is None: ... x = [] ... x.append(1) ... print x ... >>> foo() [1] >>> foo() [1] >>>
End.
作者:地球的外星人君
來源:知乎