Write Idiom Python

Python支持链式比较

# bad
a = 5
if a > 1 and a < 7:
    pass
# good
if 1 < a < 7:
    pass

Python交换变量

# bad
x = 10
y = 5
tmp = x
x = y
y = tmp

# good
x = 10
y = 5
x, y = y, x

Python中替代三目运算符?:

# bad
a = 10
b = 5
if a > b:
    c = a
else:
    c = b
# good
c = a if a > b else b

拼接字符列表时,用join方法去实现

# bad

格式化字符时多使用format函数

# bad
name = "tony"
age = 100
str = "myname : " + name + " my age : " + str(age)
str1 = "myname : %s my age : %d" % (name, age)
# good
str2 = "myname : {} my age {}".format(name, age)

使用列表或者字典comprehension

# bad
mylist = range(20)
odd_list = []
for e in mylist:
    if e % 2 == 1:
        odd_list.append(e)
# good
odd_list = [e for e in mylist if e % 2 == 1]

# bad
user_list = [{'name': 'lucy', 'email': 'lucy@g.com'}, {'name': 'lily', 'email': 'lily@g.com'}]
user_email = {}
for user in user_list:
    if 'email' in user:
        user_email[user['name']] = user['email']
# good
{user['name']: user['email'] for user in user_list if 'email' in user}

条件判断时,避免直接和True, False, None进行比较(==)

# bad
if l == []:
    pass
# good
if l:    # 实际调用l.__len__() == 0
    pass

# bad
if something == None:
# good, None 是单例对象
if something is None:

使用enumerate代替for循环中的index变量访问

# bad
my_container = ['lily', 'lucy', 'tom']
index = 0
for element in my_container:
    print '{} {}'.format(index, element)
    index += 1

# good
for index, element in enumerate(my_container):
    print '%d %s' % (index, element)

避免使用可变(mutable)变量作为函数参数的默认初始化值

# bad
def function(l = []):
 l.append(1)
 return l

 print function()
 print function()
 print function()

 # print
 [1]
 [1, 1]
 [1, 1, 1]

 # good 使用None作为可变对象占位符
 def function(l=None):
     if l is None:
         l = []
     l.append(1)
     return l

一切皆对象

# bad
def print_addition_table():
    for x in range(1, 3):
        for y in range(1, 3):
            print(str(x + y) + '\n')

def print_subtraction_table():
    for x in range(1, 3):
        for y in range(1, 3):
            print(str(x - y) + '\n')

def print_multiplication_table():
        for x in range(1, 3):
            for y in range(1, 3):
                print(str(x * y) + '\n')

def print_division_table():
    for x in range(1, 3):
        for y in range(1, 3):
            print(str(x / y) + '\n')

print_addition_table()
print_subtraction_table()
print_multiplication_table()
print_division_table()

# good, python一切都是对象,可以函数作为参数,类似技巧可以用来简化代码
import operator as op

def print_table(operator):
    for x in range(1, 3):
        for y in range(1, 3):
            print(str(operator(x, y)) + '\n')

for operator in (op.add, op.sub, op.mul, op.div):
    print_table(operator)

防御式编程EAFP vs LBYL

  • EAFP:easier to ask forgiveness than permission
  • LBYL:look before you leap

EAFP可以理解成一切按正常的逻辑编码,不用管可能出现的错误,等出了错误再说;而LBYL就是尽可能每写一行代码,都要提前考虑下当前的前置条件是否成立;

# LBYL
def getPersonInfo(person):
    if person == None:
        print 'person must be not null!'
    print person.info

# EAFP
def getPersonInfo(person):
    try:
        print person.info
    except NameError:
        print 'person must be not null!'

其实用EAFP风格的代码最大的好处是代码逻辑清晰,而LBYL会导致本来两句话说清楚的事,往往因为穿插了很多条件检查的语句使代码逻辑变得混乱。Python社区更提倡EAFP形式的。另外还有一个原因,在高并发场景下, if条件如果是个表达式,会造成一致性问题,这个时候必须用EAFP形式。这个可以参考Glow团队的技术博客[Glow cache structure](http://tech.glowing.com/cn/glow-cache-structure).

用dict对象完成switch...case...的功能

# bad
def apply_operation(left_operand, right_operand, operator):
    if operator == '+':
        return left_operand + right_operand
    elif operator == '-':
        return left_operand - right_operand
    elif operator == '*':
        return left_operand * right_operand
    elif operator == '/':
        return left_operand / right_operand
# good
def apply_operation(left_operand, right_operand, operator):
    import operator as op
    operator_mapper = {'+': op.add, '-': op.sub, '*': op.mul, '/': op.truediv}
    return operator_mapper[operator](left_operand, right_operand)

访问tuple的数据项时,可以用namedtuple代替index的方式访问

# bad
rows = [('lily', 20, 2000), ('lucy', 19, 2500)]
for row in rows:
    print '{}`age is {}, salary is {} '.format(row[0], row[1], row[2])

# good
from collections import  namedtuple
Employee = namedtuple('Employee', 'name, age, salary')
for row in rows:
    employee = Employee._make(row)
    print '{}`age is {}, salary is {} '.format(employee.name, employee.age, employee.salary)

用isinstance来判断对象的类型

因为在python中定义变量时,不用像其它静态语言,如java, 要指定其变量数据类型,如int = 4. 但是这并不意味在python中没有数据类型,只是一个变量的数据类型是在运行的时候根据具体的赋值才最终确定。比如下面的代码是计算一个对象的长度值,如果是序列类型(str,list,set,dict)的, 直接调用len方法,如果是True, False, None则返回1,如果是数值的,则返回其int值.

# bad
def get_size(some_object):
    try:
        return len(some_object)
    except TypeError:
        if some_object in (True, False, None):
        return 1
    else:
        return int(some_object)

print(get_size('hello'))
print(get_size([1, 2, 3, 4, 5]))
print(get_size(10.0))

# good
def get_size(some_object):
    if isinstance(some_object, (list, dict, str, tuple)):
        return len(some_object)
    elif isinstance(some_object, (bool, type(None))):
        return 1
    elif isinstance(some_object, (int, float)):
        return int(some_object)

用with管理操作资源的上下文环境

在一个比较典型的场景里,如数据库操作,我们操作connection时一般要正常关闭连接,而不管是正常退出还是异常退出。如下:

# bad
class Connection(object):
def execute(self, sql):
    raise Exception('ohoh, exception!')

def close(self):
    print 'closed the Connection'

try:
    conn = Connection()
    conn.execute('select * from t_users')
finally:
    conn.close()

# good
class Connection(object):
def execute(self, sql):
    raise Exception('ohoh, exception!')

def close(self):
    print 'closed the Connection'

def __enter__(self):
    return self

def __exit__(self, errorType, errorValue, error):
    self.close()

with Connection() as conn:
    conn.execute('select * from t_users')

使用generator返回耗费内存的对象

# bad
def f():
    # ...
    return biglist

# good
def f():
    # ...
    for i in biglist:
        yield i