Python 2.7
前言
從很多地方搬運+總結,以後根據這個標準再將python的一些奇技淫巧結合起來,寫出更pythonic的代碼~
PEP8 編碼規範
以下是@bobo的整理,原文請見PEP8 Python 編碼規範整理
代碼編排
- 縮進。4個空格的縮進(編輯器都可以完成此功能),不使用Tap,更不能混合使用Tap和空格。
- 每行最大長度79,換行可以使用反斜槓,最好使用圓括號。換行點要在操作符的後邊敲回車。
- 類和top-level函數定義之間空兩行;類中的方法定義之間空一行;函數內邏輯無關段落之間空一行;其他地方儘量不要再空行。
文檔編排
- 模塊內容的順序:模塊說明和docstring—import—globals&constants—其他定義。其中import部分,又按標準、三方和自己編寫順序依次排放,之間空一行
- 不要在一句import中多個庫,比如import os, sys不推薦
- 如果採用from XX import XX引用庫,可以省略‘module.’,都是可能出現命名衝突,這時就要採用import XX
空格的使用
- 各種右括號前不要加空格。
- 逗號、冒號、分號前不要加空格。
- 函數的左括號前不要加空格。如Func(1)
- 序列的左括號前不要加空格。如list[2]
- 操作符左右各加一個空格,不要爲了對齊增加空格
- 函數默認參數使用的賦值符左右省略空格
- 不要將多句語句寫在同一行,儘管使用‘;’允許
- if/for/while語句中,即使執行語句只有一句,也必須另起一行
註釋
總體原則,錯誤的註釋不如沒有註釋。所以當一段代碼發生變化時,第一件事就是要修改註釋!註釋必須使用英文,最好是完整的句子,首字母大寫,句後要有結束符,結束符後跟兩個空格,開始下一句。如果是短語,可以省略結束符。
- 塊註釋,在一段代碼前增加的註釋。在‘#’後加一空格。段落之間以只有‘#’的行間隔。比如:
# Description : Module config.
#
# Input : None
#
# Output : None
- 行註釋,在一句代碼後加註釋。比如:
x = x + 1 # Increment x
但是這種方式儘量少使用。 - 避免無謂的註釋。
文檔描述
- 爲所有的共有模塊、函數、類、方法寫docstrings;非共有的沒有必要,但是可以寫註釋(在def的下一行)。
- 單行註釋請參考如下方式
def kos_root():
"""Return the pathname of the KOS root directory."""
global _kos_root
if _kos_root: return _kos_root
...
命名規則
* 總體原則,新編代碼必須按下面命名風格進行,現有庫的編碼儘量保持風格。絕不要單獨使用例如大寫的’i’和大寫的’o’*
- 模塊命名儘量短小,使用全部小寫的方式,可以使用下劃線。
- 包命名儘量短小,使用全部小寫的方式,不可以使用下劃線。
- 類的命名使用CapWords的方式,模塊內部使用的類採用_CapWords的方式。
- 異常命名使用CapWords+Error後綴的方式。
- 全局變量儘量只在模塊內有效,類似C語言中的static。實現方法有兩種,一是all機制;二是前綴一個下劃線。
- 函數命名使用全部小寫的方式,可以使用下劃線。
- 常量命名使用全部大寫的方式,可以使用下劃線。
- 類的屬性(方法和變量)命名使用全部小寫的方式,可以使用下劃線。
- 類的屬性有3種作用域public、non-public和subclass API,可以理解成C++中的public、private、protected,non-public屬性前,前綴一條下劃線。
- 類的屬性若與關鍵字名字衝突,後綴一下劃線,儘量不要使用縮略等其他方式。
- 爲避免與子類屬性命名衝突,在類的一些屬性前,前綴兩條下劃線。比如:類Foo中聲明__a,訪問時,只能通過Foo._Foo__a,避免歧義。如果子類也叫Foo,那就無能爲力了。
- 類的方法第一個參數必須是self,而靜態方法第一個參數必須是cls。
編程建議
- 編碼中考慮到其他python實現的效率等問題,比如運算符‘+’在CPython(Python)中效率很高,都是Jython中卻非常低,所以應該採用.join()的方式。
- 儘可能使用‘is’‘is not’取代‘==’,比如if x is not None 要優於if x
- 使用基於類的異常,每個模塊或包都有自己的異常類,此異常類繼承自Exception。
- 常中不要使用裸露的except,except後跟具體的exceptions。例如
try:
...
except Exception as ex:
print ex
異常中try的代碼儘可能少。
使用startswith() and endswith()代替切片進行序列前綴或後綴的檢查。
foo = 'abc000xyz'
if foo.startswith('abc') and foo.endswith('xyz'):
print 'yes'
else:
print 'no'
#yes
#而如下的方式不提倡
if foo[:3]=='abc' and foo[-3:]=='xyz':
print 'yes'
else:
print 'no'
- 使用isinstance()比較對象的類型。比如:
foo = 'abc000xyz'
# 提倡
print isinstance(foo,int) # false
# 不提倡
print type(foo) == type('1') #true
- 判斷序列空或不空,有如下規則:
foo = 'abc000xyz'
if foo:
print "not empty"
else:
print "empty"
#不提倡使用如下
if len(foo):
print "not empty"
else:
print "empty"
- 二進制數據判斷使用 if boolvalue的方式。
給自己的代碼打分
使用pylint進行代碼檢查,@permilk–Python代碼分析工具:PyChecker、Pylint
# 安裝
pip install pylint
寫一段測試代碼,命名爲test.py
# -*- coding:utf-8 -*-
# 原理:http://blog.csdn.net/morewindows/article/details/6684558
# 代碼提供: http://www.cnblogs.com/yekwol/p/5778040.html
def parttion(vec, left, right):
key = vec[left]
low = left
high = right
while low < high:
while (low < high) and (vec[high] >= key):
high -= 1
vec[low] = vec[high]
while (low < high) and (vec[low] <= key):
low += 1
vec[high] = vec[low]
vec[low] = key
return low
# 採用遞歸的方式進行函數構建
def quicksort(vec, left, right):
if left < right:
p = parttion(vec, left, right)
quicksort(vec, left, p-1) # 再同樣處理分片問題
quicksort(vec, p+1, right)
return vec
#s = [6, 8, 1, 4, 3, 9, 5, 4, 11, 2, 2, 15, 6]
before_list = [4, 6, 1, 3, 5, 9]
print "before sort:", before_list
after_list = quicksort(before_list, left=0, right=len(before_list)-1)
print"after sort:", after_list
進行代碼規範測試
# 使用
pylint test.py
# 輸出
Problem importing module variables.py: No module named functools_lru_cache
Problem importing module variables.pyc: No module named functools_lru_cache
No config file found, using default configuration
************* Module test
C: 24, 0: Trailing whitespace (trailing-whitespace)
C: 1, 0: Missing module docstring (missing-docstring)
C: 5, 0: Missing function docstring (missing-docstring)
C: 20, 0: Missing function docstring (missing-docstring)
C: 22, 8: Invalid variable name "p" (invalid-name)
C: 28, 0: Invalid constant name "before_list" (invalid-name)
C: 30, 0: Invalid constant name "after_list" (invalid-name)
Report
======
23 statements analysed.
Statistics by type
------------------
+---------+-------+-----------+-----------+------------+---------+
|type |number |old number |difference |%documented |%badname |
+=========+=======+===========+===========+============+=========+
|module |1 |1 |= |0.00 |0.00 |
+---------+-------+-----------+-----------+------------+---------+
|class |0 |0 |= |0 |0 |
+---------+-------+-----------+-----------+------------+---------+
|method |0 |0 |= |0 |0 |
+---------+-------+-----------+-----------+------------+---------+
|function |2 |2 |= |0.00 |0.00 |
+---------+-------+-----------+-----------+------------+---------+
Raw metrics
-----------
+----------+-------+------+---------+-----------+
|type |number |% |previous |difference |
+==========+=======+======+=========+===========+
|code |23 |71.88 |23 |= |
+----------+-------+------+---------+-----------+
|docstring |0 |0.00 |0 |= |
+----------+-------+------+---------+-----------+
|comment |5 |15.62 |5 |= |
+----------+-------+------+---------+-----------+
|empty |4 |12.50 |4 |= |
+----------+-------+------+---------+-----------+
Duplication
-----------
+-------------------------+------+---------+-----------+
| |now |previous |difference |
+=========================+======+=========+===========+
|nb duplicated lines |0 |0 |= |
+-------------------------+------+---------+-----------+
|percent duplicated lines |0.000 |0.000 |= |
+-------------------------+------+---------+-----------+
Messages by category
--------------------
+-----------+-------+---------+-----------+
|type |number |previous |difference |
+===========+=======+=========+===========+
|convention |7 |7 |= |
+-----------+-------+---------+-----------+
|refactor |0 |0 |= |
+-----------+-------+---------+-----------+
|warning |0 |0 |= |
+-----------+-------+---------+-----------+
|error |0 |0 |= |
+-----------+-------+---------+-----------+
Messages
--------
+--------------------+------------+
|message id |occurrences |
+====================+============+
|missing-docstring |3 |
+--------------------+------------+
|invalid-name |3 |
+--------------------+------------+
|trailing-whitespace |1 |
+--------------------+------------+
Global evaluation
-----------------
Your code has been rated at 6.96/10 (previous run: 6.96/10, +0.00)
Python奇技淫巧
使用 Counter 進行計數統計
>>> from collections import Counter
>>> Counter(s=3, c=2, e=1, u=1)
Counter({'s': 3, 'c': 2, 'u': 1, 'e': 1})
>>> some_data=('c', '2', 2, 3, 5, 'c', 'd', 4, 5, 'd', 'd')
>>> Counter(some_data).most_common(2)
[('d', 3), ('c', 2)]
>>> some_data=['c', '2', 2, 3, 5, 'c', 'd', 4, 5, 'd', 'd']
>>> Counter(some_data).most_common(2)
[('d', 3), ('c', 2)]
>>> some_data={'c', '2', 2, 3, 5, 'c', 'd', 4, 5, 'd', 'd'}
>>> Counter(some_data).most_common(2)
[('c', 1), (3, 1)]
enumerate獲取鍵值對
- 在同時需要index和value值的時候可以使用 enumerate。下列分別將字符串,數組,列表與字典遍歷序列中的元素以及它們的下標
>>> for i,j in enumerate('abcde'):
... print i,j
...
0 a
1 b
2 c
3 d
4 e
>>> for i,j in enumerate([1,2,3,4]):
... print i,j
...
0 1
1 2
2 3
3 4
>>> for i,j in enumerate([1,2,3,4],start=1):
... print i,j
...
1 1
2 2
3 3
4 4
# 通過鍵索引來追蹤元素
from collections import defaultdict
s = "the quick brown fox jumps over the lazy dog"
words = s.split()
location = defaultdict(list)
for m, n in enumerate(words):
location[n].append(m)
print location
# defaultdict(<type 'list'>, {'brown': [2], 'lazy': [7], 'over': [5], 'fox': [3],
# 'dog': [8], 'quick': [1], 'the': [0, 6], 'jumps': [4]})
os.path的使用
- os.path.join用於拼接路徑,好處是可以根據系統自動選擇正確的路徑分隔符”/”或”\”
- os.path.split 把路徑分割成dirname和basename,返回一個元組
- os.listdir 獲取路徑下所有文件,返回list
import os
path=os.path.abspath("ams8B.zip")
print path # /Users/didi/Desktop/testhelp/ams8B.zip # 實際上該文件夾下沒有ams8B.zip
print os.path.join("/".join(path.split("/")[:-1]),'ams8B.gz') # /Users/didi/Desktop/testhelp/ams8B.gz
print os.path.join("home","user","test") # home/user/test
# 把路徑分割成dirname和basename,返回一個元組,作用在於不用區別到底是'\'還是'/'
print os.path.split(path) # ('/Users/didi/Desktop/testhelp', 'ams8B.zip')
print os.path.join(os.path.split(path)[0],'ams8B.gz') # /Users/didi/Desktop/testhelp/ams8B.gz
print os.getcwd() # /Users/didi/Desktop/testhelp
print os.listdir(os.getcwd()) # ['t1.txt', 'test1.py', 'test2.py', '\xe6\x8e\xa5\xe9\xa9\xbeeta\xe5\x88\x86\xe5\xb8\x83.sh']
善用列表推導式
>>> foo = [2, 18, 9, 22, 17, 24, 8, 12, 27]
>>> print filter(lambda x: x % 3 == 0, foo)
[18, 9, 24, 12, 27]
>>>
>>> print map(lambda x: x * 2 + 10, foo)
[14, 46, 28, 54, 44, 58, 26, 34, 64]
>>>
>>> print reduce(lambda x, y: x + y, foo)
139
使用列表推導式
>>> [x * 2 + 10 for x in foo]
[14, 46, 28, 54, 44, 58, 26, 34, 64]
>>> [x for x in foo if x % 3 == 0]
[18, 9, 24, 12, 27]
對於輕量級循環,可儘量使用列表推導式,熟練使用列表推導式可以很多情況下代替map,filter等
>>> foo = [2, 18, 9, 22, 17, 24, 8, 12, 27]
>>> foo
[2, 18, 9, 22, 17, 24, 8, 12, 27]
>>> ['>3' if i>3 else '<3' for i in foo]
['<3', '>3', '>3', '>3', '>3', '>3', '>3', '>3', '>3']
>>> t=map(lambda x:'<3' if x<3 else '>3',foo)
>>> t
['<3', '>3', '>3', '>3', '>3', '>3', '>3', '>3', '>3']
>>>
可參考:最簡單的理解lambda,map,reduce,filter,列表推導式
sort 與 sorted
# 函數原型
sorted(iterable[, cmp[, key[, reverse]]]) # 返回一個排序後的列表
s.sort([cmp[, key[, reverse]]]) # 直接修改原列表,返回爲None
>>> persons = [{'name': 'Jon', 'age': 32}, {'name': 'Alan', 'age': 50}, {'name': 'Bob', 'age': 23}]
>>> sorted(persons, key=lambda x: (x['name'], -x['age']))
[{'name': 'Alan', 'age': 50}, {'name': 'Bob', 'age': 23}, {'name': 'Jon', 'age': 32}]
>>> a = (1, 2, 4, 2, 3)
>>> sorted(a)
[1, 2, 2, 3, 4]
>>> students = [('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10),]
>>> sorted(students, key=lambda student : student[2]) # sort by age
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
所以如果實際過程中需要保留原有列表,可以使用sorted()。sort()不需要複製原有列表,消耗內存較小,效率較高。同時傳入參數key比傳入參數cmp效率要高,cmp傳入的函數在整個排序過程中會調用多次,而key針對每個元素僅作一次處理。
關於cmp的使用,這位哥們總算踩到點python中sort()方法自定義cmp PythonTip-最大正整數
# cmp --如果排序的元素是其他類型的,如果a邏輯小於b,函數返回負數;a邏輯等於b,函數返回0;a邏輯大於b,函數返回正數就行了,這決定着兩者是否交換位置
def Reverse(a,b):
return b-a
list_ = [5,3,4,1,2]
new = sorted(list_,cmp=ps)
print new # [5, 4, 3, 2, 1]
#
# 這裏的例子是,5,3做差值,爲負,故兩者不交換位置,裏面的return作爲條件
善用traceback 追蹤深層錯誤
import traceback
try:
do something
except Exception as ex:
print ex
traceback.print_exc()
切片操作[]
相當於淺copy的作用
>>> a=[1,2,3]
>>> b=a[:]
>>> b.append(4)
>>> b
[1, 2, 3, 4]
>>> a
[1, 2, 3]
>>> import copy
>>> c=copy.copy(a)
>>> c
[1, 2, 3]
>>> c.append(4)
>>> a
[1, 2, 3]
>>> c
[1, 2, 3, 4]
>>> d=a
>>> d.append(4)
>>> d
[1, 2, 3, 4]
>>> a
[1, 2, 3, 4]
# 這裏順便說下deepcopy
# [理論部分可以參考這裏](http://www.cnblogs.com/wait123/archive/2011/10/10/2206580.html)
# 淺copy
>>> import copy
>>> a = [[1,2],3,4]
>>> b = copy.copy(a)
>>> id(a)
54936008L
>>> id(b)
54964680L
>>> a is b
False
>>> b
[[1, 2], 3, 4]
>>> b[0][1]
2
>>> b[0][1]=2333
>>> b
[[1, 2333], 3, 4]
>>> a
[[1, 2333], 3, 4]
# deepcopy
>>> a = [[1,2],3,4]
>>> c = copy.deepcopy(a)
>>> id(a)
55104008L
>>> id(c)
54974536L
>>> a is c
False
>>> c
[[1, 2], 3, 4]
>>> c[0][1]
2
>>> c[0][1]=233
>>> c
[[1, 233], 3, 4]
>>> a
[[1, 2], 3, 4] # 不會隨之改變
# 這裏測試下切片操作相當於淺copy
>>> d = a[:]
>>> d
[[1, 2], 3, 4]
>>> d[0][1]=0
>>> d
[[1, 0], 3, 4]
>>> a
[[1, 0], 3, 4] # 會隨之改變
進行逆序排列
>>> b
[1, 2, 3, 4]
>>> b[::-1]
[4, 3, 2, 1]
json.dump()/loads() 存儲字典結構
# json.dumps : dict轉成str
# json.loads:str轉成dict
import json
dict_ = {1:2, 3:4, "55":"66"}
json_str = json.dumps(dict_)
print type(json_str), json_str
# <type 'str'> {"55": "66", "1": 2, "3": 4}
print type(json.loads(json_str))
# <type 'dict'> {u'55': u'66', u'1': 2, u'3': 4}
pprint打印結構
from pprint import pprint
data = [(1,{'a':'A','b':'B','c':'C','d':'D'}),
(2,{'e':'E','f':'F','g':'G','h':'H',
'i':'I','j':'J','k':'K','l':'L'
}),]
print data
pprint(data)
#print效果
[(1, {'a': 'A', 'c': 'C', 'b': 'B', 'd': 'D'}), (2, {'e': 'E', 'g': 'G', 'f': 'F', 'i': 'I', 'h': 'H', 'k': 'K', 'j': 'J', 'l': 'L'})]
# pprint效果
[(1, {'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D'}),
(2,
{'e': 'E',
'f': 'F',
'g': 'G',
'h': 'H',
'i': 'I',
'j': 'J',
'k': 'K',
'l': 'L'})]
zip打包元組
定義:zip([seql, …])接受一系列可迭代對象作爲參數,將對象中對應的元素打包成一個個tuple(元組),然後返回由這些tuples組成的list(列表)。若傳入參數的長度不等,則返回list的長度和參數中長度最短的對象相同。
#!/usr/bin/python
# -*- coding: utf-8 -*-
name = ['mrlevo','hasky']
kind = ['human','dog']
z1 = [1,2,3]
z2 = [4,5,6]
result = zip(z1,z2) # 壓縮過程
uzip= zip(*result) # 解壓過程,拆分爲元組
print "the zip:",result
# the zip: [(1, 4), (2, 5), (3, 6)]
print "the uzip:",uzip
#the uzip: [(1, 2, 3), (4, 5, 6)]
print "the uzip part of z1:%s\nthe uzip part of z2:%s"%(str(uzip[0]),str(uzip[1]))
#the uzip part of z1:(1, 2, 3)
#the uzip part of z2:(4, 5, 6)
*args和**kw
*args僅僅只是用在函數定義的時候用來表示位置參數應該存儲在變量args裏面。Python允許我們制定一些參數並且通過args捕獲其他所有剩餘的未被捕捉的位置。當調用一個函數的時候,一個用*標誌的變量意思是變量裏面的內容需要被提取出來然後當做位置參數被使用。
def add(x, y):
return x + y
list_ = [1,2]
add(list_[0], list_[1]) # 3
add(*list_) # 3
*args要麼是表示調用方法大的時候額外的參數可以從一個可迭代列表中取得,要麼就是定義方法的時候標誌這個方法能夠接受任意的位置參數。接下來提到的**,**kw代表着鍵值對的字典,也就是說,你不用一個個字典用key來取value了
dict_ = {'x': 1, 'y': 2, 'z':3}
def bar(x, y, z):
return x + y + z
bar(**dict_) # 6
bar(dict_['x'],dict_['y'],dict_['z']) # 6
內建函數 itertools優雅的迭代器
方法很多,就先介紹筆試題碰到的permutations
permutations(p[,r]);返回p中任意取r個元素做排列的元組的迭代器
如:permutations(‘abc’, 2) # 從’abcd’中挑選兩個元素,比如ab, bc, … 將所有結果排序,返回爲新的循環器。
注意,上面的組合分順序,即ab, ba都返回。
combinations(‘abc’, 2) # 從’abcd’中挑選兩個元素,比如ab, bc, … 將所有結果排序,返回爲新的循環器。
注意,上面的組合不分順序,即ab, ba的話,只返回一個ab。
再來個實際點的算法的一個例子,雖然毫無算法結構可言
# 輸入一個字符串,按字典序打印出該字符串中字符的所有排列。例如輸入字符串abc,則打印出由字符a,b,c所能排列出來的所有字符串abc,acb,bac,bca,cab和cba
# itertools,內建庫的用法
# 參考:http://blog.csdn.net/neweastsun/article/details/51965226
import itertools
def Permutation(ss):
# write code here
if not ss:
return []
return sorted(list(set(map(lambda x:''.join(x), itertools.permutations(ss)))))
Permutation('abc')
# ['abc', 'acb', 'bac', 'bca', 'cab', 'cba']
更多可以參考:Python標準庫13 循環器 (itertools)
使用with open() as f 來讀取文件
對於文本的讀取,使用f=open(‘path’)的方法來說,侷限性很大,第一是內存加載問題,第二是文件流打開之後最後還需要關閉的問題,使用with..as輕鬆解決
with open('path') as f:
for line in f:
do songthing
# for line in f 這種用法是把文件對象f當作迭代對象,系統將自動處理IO緩存和內存管理。對於讀取超大的文件,這個方法不會把內存撐爆,這個是按行讀取的
with open('path') as f:
for line in f.readlines():
do something
# 這個方法是將文本中的內容都放在了一個列表裏,然後進行迭代,對於大量的數據而言,效果不好
使用yield節省內存開銷
[1,2,3,4]這個是迭代器,用for來迭代它,生成器(x for x in range(4))也是迭代器的一種,但是你只能迭代它們一次.原因很簡單,因爲它們不是全部存在內存裏,它們只在要調用的時候在內存裏生成,
Yield
的用法和關鍵字return
差不多,下面的函數將會返回一個生成器。迭代的時候碰到yield立刻return一個值,下一次迭代的時候,從yield的下一條語句開始執行
>>> mygenerator = (x*x for x in range(4))
>>> mygenerator
<generator object <genexpr> at 0x1121b55a0>
>>> mygenerator.next()
0
>>> mygenerator.next()
1
----------------------
>>> def generater(n):
... for i in range(n):
... yield i
... print 'here'
...
>>> g = generater(5)
>>> g
<generator object generater at 0x10c801280> # 凡是帶有yield的函數都變成了生成器,都可以被迭代next()使用,
>>> g.next()
0
>>> g.next()
here
1
>>> g.next()
here
2
# 這裏說明了它的運作過程,第一次迭代的時候,運行到yield函數,進行返回,而不執行下一個動作,第二次迭代的時候,直接從上一次yield的地方的下一條語句進行執行,也就看到了如下的效果。
另一個 yield 的例子來源於文件讀取。如果直接對文件對象調用 read() 方法,會導致不可預測的內存佔用。好的方法是利用固定長度的緩衝區來不斷讀取文件內容。通過 yield,我們不再需要編寫讀文件的迭代類,就可以輕鬆實現文件讀取:
# from 廖雪峯
def read_file(fpath):
BLOCK_SIZE = 1024
with open(fpath, 'rb') as f:
while True:
block = f.read(BLOCK_SIZE)
if block:
yield block
else:
return
更新序列
- 2017.04.04 第一次更新:PEP8編程規範
- 2017.05.10 第二次更新:常用技巧
- 2017.05.18 第三次更新:pylint檢查
- 2017.06.14 第四次更新:常用技巧
- 2017.07.02 第五次更新:常用技巧
- 2017.07.06 第五次更新:常用技巧
- 2017.07.15 第六次更新:常用技巧
致謝
@馭風者–優雅的python
@bobo–PEP8 Python 編碼規範整理
@mrlevo520–實習點滴
@轉–深入淺出 Python 裝飾器:16 步輕鬆搞定 Python 裝飾器
@轉–【Python】 sorted函數
@謝軍–python中sort()方法自定義cmp PythonTip-最大正整數
@guohuino2–python 如何讀取大文件
@轉–Python中的高級數據結構