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中的高级数据结构