第一章 用pythonic方式来思考
- 理解Pythonic概念:
def quicksort(array):
less = [];greater=[]
if len (array) <=1:
return array
pivot = array.pop()
for i in array:
if x <= pivot:less.append(x)
else: greater.append(x)
return quciksort(less)+[pivot]+quciksort(greater)
简洁的代码风格:a,b=b,a
;简洁的遍历。简洁的SLice语法
编写pythonic代码
(1)避免劣化代码:
避免用大小写来区分不同对象
避免使用容易混淆的名称
- 不要害怕太长的变量名
(2)深入认识python………
PEP检查代码规范。。。。(不如pycharm)
pip install -U pep8 pep8 --first 文件名.py
理解python与C语言的不同之处
(1)“缩进”与”{}”
(2)’与”
(3)三元操作符?:[ python中 还可以表示为
x if c else b
](4)switch..case[python没有,那又怎样?]
在代码中适当添加注释
- 使用块或者行注释的时候注释复杂操作和算法
- 注释和代码隔开一定距离,同时在块注释之后最好多留几行空白写代码。
- 给外部访问的函数和方法添加文档注释(docstring)
- 推荐在头文件中包含copyright申明、模块描述
"""Licensed Materials -Property of CorpA (C) Copyright A corp.1999,2016 All Rights Reversed --------------------------------------------------------------- File Name : Description : Author : --------------------------------------------------------------- """
适当添加空行的使代码布局更优雅、合理
基本遵循:
1)在一组代码表达完一个完整的思路之后,应该用空白进行间隔。
2)尽量保持上下文语义的易理解性。(如调用者放在上面,被调用者放在下面)
3)避免过长的代码行
4)不要为了保持水平的人使用多余的空格
5)空格的使用要能够在需要强调的时候警示读者
if x == 4: print x,y; x,y = y,x
编写函数4原则:
- 函数设计要劲量短小(避免过长函数),嵌套层次不宜过深(不超过三层)
- 函数申明应该做到合理简单易于使用,参数个数不宜过多。
- 参数设计应该考虑向下兼容(如
def readfile(filename,logger=logger.info):
) - 一个函数只做一件事,保持函数语句粒度的一致性,低耦合性。
将常量集中到一个文件
这里切换到Effective Python 59个有效方法:
建议二:PEP 8风格指南
使用space(空格)来表示缩进进,而不要用 tab(制表符).
和语法相关的每一层缩进都用 4 个空格来表示
或者用pycharm
每行字符串不超过79
文件中的函数与类之间用两个空行隔开
同一个类中,各方法之间应该用一个空行隔开。
在使用下标获取列表元素、调用函数或给出关键字参数赋值的时候,不要在两旁加space
为变量赋值的时候,赋值符号左侧和右侧各自写上一个空格。
命名:
函数、变量及属性应该用小写字母来拼写,各单词之间以下划线相连(hello_world)
受保护的实例属性,如_name
私有的实例属性,如__name
类与异常,应该全部采用大写字母,如RP_ERROR
类中的实例方法,应该把首个参数命名为self,以表示该对象自身。
类方法的首个参数,应该命名为cls,以表示自身。
表达式与语句
采用内联式的否定词,而不要把否定词放在整个表达式的前面
if a is not b
而不是(你的离散)if not a is b
不要通过检测长度的方法(if len(onelist)==0)来判断somelist是否为[]或“”等空值,而是应该采用
if not onelist:
来判断(同理if onelist:
)不要编写单行的if语句、for/while/except语句,而是应该把这些语句分成多行,装逼过多必自毙。
import语句应该总是放在文件开头
引入模块,总是应该使用绝对名称,而不应该根据当前模块的路径来使用想对名称
from bar import foo
而不是import foo
如果一定要以相对名称来编写import,就
from.import foo
文件中的那些import语句应该按顺序分为三个部分,各个import语句应该按模块的字母顺序来排序。
<完了>
第三条:了解bytes、str、unicode的区别
bytes包含原生8位值;str包含Unicode字符。
把Unicode字符表示为二进制数据可以采用UTF-8编码格式(记住必须使用encode和decode方法)
一定要把编码和解码放在界面最外围来做
附上原书的两个辅助函数:
def to_str(bytes_or_str): if isinstance(bytes_or_str,bytes): value = bytes_or_str.decode('utf-8') else: value = bytes_or_str return value # Istance of str
def to_bytes(bytes_or_str): if isinstance(bytes_or_str,str): value = bytes_or_str.encode('utf-8') else: value = bytes_or_str return value # Istance of bytes
python3中可能出现的问题:如果通过内置的open函数获取了文件句柄,会默认采用UTF-8编码格式。与python2相反。
不能以>或+等操作符来混同操作bytes和str实例
读取二进制数据,或写入二进制数据时,总应该以‘rb’’wb’等模式来开启文件
第四条:用辅助函数来取代复杂的表达式
附上书上的代码。。。
from urllib.parse import parse_qs
my_values = parse_qs('red=5&blue=0&green=',
keep_blank_values=True)
print(repr(my_values))
用get方法(接着上面):
print('Red: ',my_values.get('red'))
print('Opacity ',my_values.get('opacity'))
下一个返回的是None
red = my_values.get('red',[''])[0] or 0
green = my_values.get('green',[''])[0] or 0
Opacity = my_values.get('Opacity',[''])[0] or 0
print('Red: {}'.format(red))
print('Greed: {}'.format(green))
print('Opacity {}'.format(opacity))
这样的代码很难阅读。。。。。
red = int(my_values.get('red',[''])[0] or 0)
这样也是。。。。。
so red = my_values.get('red',[''])
red = int(red[0]) if red[0] else 0
这样的三元操作符。
如果需要频繁操作的话:
def get_first_int(values,key,default=0):
found = values.get(key,[''])
if found[0]:
found = int(found[0])
else:
found = default
return found
so green = get_first_int(my_values,'green')
这样就更加清晰了
要点:误过度运用python语法特性;把复杂表达式移入辅助函数,反复使用相同逻辑;使用if/else优于or/and
第五条:了解切割序列的办法
基本写法是somelist[start:end],start包括在内end元素不包括在内。
a = ['a','b','c','d','e']
print('First_Four:',a[:4])
print('Last_Four:',a[-4:])
print('Middle_One:'a[2:-2])
#下面是断言
assert a[:5] == a[0:5]
assert a[5:] == a[5:len(a)]
##越界也没问题
###单个元素时,下标不能越界,否则IndexError
a[2:4]=[1]
b = a
assert b == a and b is a
c=a[:]
assert c == a and c is not a
#浅拷贝与深拷贝
要点:不要多余的代码,start索引为0,或end索引为序列长度应省略
切片操作不计较是否越界;对list赋值,切片操作可以替换,即使场不同也可以替换。
第六条:在单次切片操作内,不要同时指定start、end、stride
除了基本切片操作,还有onelist[start:end:stride]
#反转
onelist = [i for i in range(10)]
onelist = onelist[::-1]
这种技巧对字节串和ASCII字符有用,但是对已经编码成UTF-8字节串的Unicode字符串来说,则无法奏效。
w = "你好"
x = w.encode('utf-8')
y = x[::-1]
z = y.decode('utf-8')
自己尝试把。
itertools模块中也有一个islide方法,但这个方法不允许为start、end、stride指定负值。
要点:既有start、end、stride的切割操作,可能会令人费解;尽量使用stride为正数,且不带start或end索引的切割操作。尽量避免用负数做stride;不要同时使用start、end和stride。如果需要则拆解。
第七条:用列表来取代map和filter
squares = [x**2 for x in range(1,11) if x % 2 == 0]
another_squares = list(map(lambda x: x**2, filter(lambda x: x % 2 == 0, a)))
#你可以对比一下。。。。。。
字典与集合也有类似的推导机制。
##附上书上的代码
chile_ranks = {'ghost': 1, 'habanero': 2, 'cayenne': 3}
rank_dict = {rank: name for name, rank in chile_ranks.items()}
chile_len_set = {len(name) for name in rank_dict.values()}
print(rank_dict)
print(chile_len_set)
要点:列表推导要比map和filter函数清晰;列表推导可以跳过输入列表中的某些元素,如果改用map来做,那就必须辅以filter才能实现。
第八条:不要使用含有两个以上表达式的列表推导
列表推导支持多重循环。
matrix = [[1,2,3], [4,5,6], [7,8,9]]
flat_matrix = [x for row in matrix
for x in row]
#当然也可以这样(建议)
flat_matrix = []
for row in matrix:
flat_matrix.extend(row)
当然也有多重if。。。。。
matrix = [[1,2,3], [4,5,6], [7,8,9]]
flat_matrix = [x for row in matrix if sum(row) >= 10
for x in row if x % 3 == 0]
不是太建议这样。。。
要点:列表推导支持多级循环,每一级循环也支持多项条件;抄过两个表达式的列表推导应尽量避免。
第九条:用生成器表达式来改写数据量较大的列表推导
假设你要打开一个很大的全新的列表,这时会消耗大量内存而导致程序崩溃。你读取一份文件并返回每行的字符数,若采用列表推导来做,则需要把文件的每一行的长度都保存在内存中,文件特别大时,无休止的network socket来读取。很容易产生问题。
#不推荐
fo = open('C:\\Users\\Desktop\\Biglist.txt','r+')
value = [len(x) for x in fo]
#推荐
it = (len(x) for x in fo)
print(next(it))
要点:当输入的数据量较大时,列表推导可能会占用太多内存;有生成器表达式所返回的迭代器,可以逐次产生输入值,避免上述问题;串在一起的生成器表达式执行速度很快
第十条:尽量用enumerate 取代 range
虽然range函数很有用。
import random
random_bits = 0
for i in range(64):
if random.randint(0,1):
random_bits |= 1 << i
flavor_list = ['vanilla', 'chocolate', 'pecan','strawberry']
for flavor in flavor_list:
print('{} is delicious'.format(flavor))
#原来这样
for i in range(len(flavor_list)):
flavor = flavor_list[i]
print('{}: {}'.format(i + 1, flavor))
#现在推荐
for i, flavor in enumerate(flavor_list):
print("{} : {}".format(i + 1, flavor))
#也可以改成这样
for i, flavor in enumerate(flavor_list,1):
print("{} : {}".format(i, flavor))
要点:enumerate函数提供了一种精简的写法,可以在便利迭代器时获得索引。可以给enumerate提供第二个参数,替代range与小标访问相结合的序列。
第十一条:用zip函数同时遍历两个迭代器
在编写python代码,通常要面对很多列表,而这些列表里的对象,可能是相互关联。
names = ['Cecilia', 'Lise', 'Marie']
letters =[len(n) for n in names]
longest_name = None
max_letters = 0
for i in range(len(names)):
count = letters[i]
if count > max_letters:
longest_name = names[i]
max_letters = count
print(longest_name)
#用enumerate改正:
for i, name in enumerate(names):
count = letters[i]
if count > max_letters:
longest_name = name
max_letters = count
#用zip简化:
for name, count in zip(names, letters):
if count > max_letters:
longest_name = name
max_letters = count
Q1:Python2中并不是生成器,请用itertools中的izip函数
Q2:输入的迭代器长度不同,问题如下
names.append('huchi')
for name, count in zip(names, letters):
print(name)
只要耗尽一个迭代器就停止了,若不能确定zip所封装的列表是否等长。可以用itertools中的zip_longest函数(python2:izip_longest).
要点:zip可以平行遍历多个迭代器;python3中的zip相当于生成器。迭代器长度不等,zip会自动提前终止;itertools中zip_longest函数可以平行遍历多个迭代器,不用在乎他们长度是否相等。
第十二条:不要再for 和while循环后面写else块
#我很反对下面的代码,但还是敲出来了
for i in range(3):
print('Loop {}'.format(i))
else:
print('Else block!')
for i in range(3):
print('Loop {}'.format(i))
if i == 1:break
else:
print('Else block!')
for x in[]:
print('Never runs')
else:
print('For Else block!')
#while同上
#对比以下代码:
a,b = 4,9
for i in range(2, min(a, b)+1):
print('Testing', i)
if a % i == 0 and b % i == 0
print('Not coprime')
break
else:
print('Coprime')
#another
def coprime(a, b):
for i in range(2, min(a, b) + 1):
if a % i == 0 and b % i == 0:
return False
return True
#Last one
def coprime2(a, b):
is_coprime = True
for i in range(2, min(a, b) + 1):
if a % i == 0 and b % i == 0:
is_coprime = False
break
return is_coprime
#循环容易理解
要点:不要用
第十三条:合理利用try/except/else/finally结构中的每个代码块
1.finally:如果要将异常向上传播,又要在异常发生时执行清理工作。
handle = open('hello.txt','r') #IOError
try:
data = handle .read() #UnicodeDecodeError
finally:
handle.close() #read出错,异常传播到上面即引发IOError
2.else块:try/except/else结构可以描述哪些异常邮资机代码处理,哪些异常传播到上级。如果try没有异常,执行else块。
#从字符串中加载JSON字典数据,然后返回字典里某个键所对应的值
import json
def load_json_key(data,key):
try:
result_dict = json.load(data) #ValueError
except ValueError as e:
raise KeyError from e
else:
return result_dict[key] #KeyError
#如果不是JSON格式,会产生ValueError
#如果可以,else中的语句就会执行,若查询有异,则异常会向上传播
3.混合使用:try/except/else/finally结构。
#附上书上的代码从文件中读取某项事务的描述信息,处理及更新
import json
UNDEFINED = object()
def divide_json(path):
"""
try读取文件并处理其内容
except来对应try可能发生的相关的异常
else块来实时更新文件并把更新中可能出现的异常回报给上级
finally来清理文件句柄
"""
handle = open(path, 'r+') #IOError
try:
data = handle.read() #UnicodeDecodeError
op = json.loads(data) #ValueError
value = (
op['numerator']/
op['denominator']) #ZeroDivisionError
except ZeroDivisionError as e:
return UNDEFINED
else:
op['result'] = value
result = json.dumps(op)
handle.seek(0)
handle.write(result) #IOError
return value
finally:
handle.close()
要点:无论try块是否发生异常,都可以利用try/finally符合语句中的finally来执行清理工作;else可以用来所见try块的代码量,并把没有发生异常时所要执行的语句与try/except代码块隔开;顺利运行try块后,若想使某些操作能在finally块的清理代码之前执行,可写在else。