编写高质量的python(第一章)

第一章 用pythonic方式来思考

  1. 理解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语法

  1. 编写pythonic代码

    (1)避免劣化代码:

    1. 避免用大小写来区分不同对象

    2. 避免使用容易混淆的名称

    3. 不要害怕太长的变量名

    (2)深入认识python………

    PEP检查代码规范。。。。(不如pycharm)

    pip install -U pep8
    pep8 --first 文件名.py
  2. 理解python与C语言的不同之处

    (1)“缩进”与”{}”

    (2)’与”

    (3)三元操作符?:[ python中 还可以表示为x if c else b]

    (4)switch..case[python没有,那又怎样?]

  3. 在代码中适当添加注释

    1. 使用块或者行注释的时候注释复杂操作和算法
    2. 注释和代码隔开一定距离,同时在块注释之后最好多留几行空白写代码。
    3. 给外部访问的函数和方法添加文档注释(docstring)
    4. 推荐在头文件中包含copyright申明、模块描述
    """Licensed Materials -Property of CorpA
    
       (C) Copyright A corp.1999,2016 All Rights Reversed
    ---------------------------------------------------------------
    
    File Name          :
    Description            :
    
    Author             :
    ---------------------------------------------------------------
    
    """
  4. 适当添加空行的使代码布局更优雅、合理

    基本遵循:

    1)在一组代码表达完一个完整的思路之后,应该用空白进行间隔。

    2)尽量保持上下文语义的易理解性。(如调用者放在上面,被调用者放在下面)

    3)避免过长的代码行

    4)不要为了保持水平的人使用多余的空格

    5)空格的使用要能够在需要强调的时候警示读者

    if x == 4: print x,y; x,y = y,x

  5. 编写函数4原则:

    1. 函数设计要劲量短小(避免过长函数),嵌套层次不宜过深(不超过三层)
    2. 函数申明应该做到合理简单易于使用,参数个数不宜过多。
    3. 参数设计应该考虑向下兼容(如def readfile(filename,logger=logger.info):)
    4. 一个函数只做一件事,保持函数语句粒度的一致性,低耦合性。
  6. 将常量集中到一个文件

这里切换到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。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值