分享书籍[writing idiomatic python ebook] 二
对多个变量设置相同的值时,用连等号一起赋值
x = 10 y = 10 z = 10
改成:
x = y = z = 10
交换变量值时,可以避免定义新的临时变量
x = 10 y = 5 temp = x x = y y = temp
改成:
x = 10 y = 5 x, y = y, x
多次调用字符对象方法时,可以用链式调用方法,避免中间产生过多变量
str1 = 'i am a bug!' str2 = str1.strip() str3 = str2.upper() str4 = str3.replace('!', '?')
改成:
str4 = str1.strip().upper().replace('!', '?')
当然,如果连续调用链过长,也会使代码不清楚,作者给的建议是最好不要超过三次调用
拼接字符列表时,用join方法去实现
mylist = ['i', 'am', 'a', 'bug'] resultStr = '' for e in mylist: resultStr += e
改成:
mylist = ['i', 'am', 'a', 'bug'] resultStr = ''.join(mylist)
格式化字符时多使用format函数
我们格式化字符时一般会用下面两种方式:
name = "tony" age = 100 str = "myname : " + name + " my age : " + str(age) str1 = "myname : %s my age : %d" % (name, age)
改成:
str2 = "myname : {} my age {}".format(name, age)
对list对象进行相关操作并生成新的对象时多使用comprehension(这个怎么翻译?)
mylist = range(20) odd_list = [] for e in mylist: if e % 2 == 1: odd_list.append(e)
改成:
mylist = range(20) odd_list = [e for e in mylist if e % 2 == 1]
这种写法性能更好
有时候使用负列表索引更方便
mylist = range(20) len_mylist = len(mylist) last_five_list = mylist[len_mylist - 5:]
改成:
last_five_list = mylist[-5:]
判断一个列表里的元素是否都为True时,可以对列表进行all操作
def contains_zero(itor): for e in itor: if e: return False return True
改成:
def contains_zero(itor): return all(itor)
all只有在列表中的每个元素都返回True时才会返回True
区别xrange和range
range会在内存中生成完整的列表实例, 而xrange则只是生成一个迭代器;
如下我只想打印一个集合中第一个偶数
for index in range(3, 1000000000): if index % 2 == 0: print index break
在我机器上会出现如下错误,就是因为range会在内存中生成完整对象实例:
Traceback (most recent call last): File "C:\Users\tony\Desktop\huawei.py", line 3, in <module> for index in range(3, 1000000000): MemoryError [Finished in 0.2s with exit code 1]
改成如下就正常了:
for index in xrange(3, 1000000000): if index % 2 == 0: print index break
用dict对象完成switch...case...的功能
在python里没有switch...case...功能。但是dict可以编写出更直观简洁的代码出来。如下,模拟一个计算器的功能,根据传入的操作符和操作数来算出结果:
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
改成:
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)
使用dict.get方法可以提供一个默认值
我们在获取dict的某个元素时,因为不确定该键是否存在,所以一般是先检查,再获取。如下:
mydict = {'a': 1} default_b = 2 if 'b' in mydict: default_b = mydict['b'] print default_b
改成:
print mydict.get('b', 2)
大家或许只知道list的comprehension,其实dict也有comprehension
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']
改成:
{user['name']: user['email'] for user in user_list if 'email' in user}
利用set的集合运算功能
在很多场景中,我们经常要从两个集合(列表)中找出相同的,相异的,或者相互排除的元素列表,这个时候我们可以利用set本身支持的各种集合运算功能。就像数学中讲的集合的:相交,相并,异合等等。
如下我们要计算出满足两个特殊的相交集:
def get_both_popular_and_active_users(): most_popular_users = get_list_of_most_popular_users() most_active_users = get_list_of_most_active_users() popular_and_active_users = [] for user in most_active_users: if user in most_popular_users: popular_and_active_users.append(user)
改成:
def get_both_popular_and_active_users(): return(set(get_list_of_most_active_users()) & set(get_list_of_most_popular_users()))
set支持的运算有: A & B, A | B, A ^ B 。还有一点要注意的,set在计算的时候如何判定两个元素相等的呢,除了要在类中定义的__eq__方法返回值相同外,还要定义 __hash__ 值相同。这点和java中的HashMap的判定行为(equals, hashCode)差不多
set的comprehension
到这里我们可以比较下list, dict, set 三种数据结构的comprehension的不同表述。list用[...], dict用{key:value...}, 而set用{...}
users_first_names = set() for user in users: users_first_names.add(user.first_name)
改成:
users_first_names = {user.first_name for user in users}
访问tuple的数据项时,可以用namedtuple代替index的方式访问
rows = [('lily', 20, 2000), ('lucy', 19, 2500)] for row in rows: print '{}`age is {}, salary is {} '.format(row[0], row[1], row[2])
改成:
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)
namedtuple方法会成一个tuple的子类,并通过类方法_make可以把一个tuple实例转换成一个可以通过name来索引的对象。这比通过index的方式更直观
作用isinstance来判断对象的类型
因为在python中定义变量时,不用像其它静态语言,如java, 要指定其变量数据类型,如int = 4. 但是这并不意味在python中没有数据类型,只是一个变量的数据类型是在运行的时候根据具体的赋值才最终确定。比如下面的代码是计算一个对象的长度值,如果是序列类型(str,list,set,dict)的, 直接调用len方法,如果是True, False, None则返回1,如果是数值的,则返回其int值.
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))
改成:
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)
这里有一点要注意,在判断对象是否是None时,用了type(None)。因为没有直接书面的形式表达None的类型。
在类中定义私有属性(private attribute)时,尽量用下划线开头的名称规范,如__name, _name
在python在,默认情况下所有的类属性,方法函数都默认是公共的(public),即外部对象可以访问。然后在有些情况下,我们需要约定一些成员属性或者方法只是内部使用,不能直接被外部修改。在python中达到这些的目的一般是通过“约定规范”来完成。比如某一个类属性以下划线(_)开头,则是显式的的约定该成员是具有私有属性的,最好不要在外部直接调用。当然这样的约定并不能阻止你这样做,但是良好素质的程序员应该都会遵守这个规定。而双下划线(__)开头的成员更具有“私有”的属性,因为在外部不能直接调用,因为python解译器把这些的成员名称进行变形处理,使外部不能轻易直接访问。来看下面一段代码:
class A(object): __i = 1 def __init__(self): self._j = 2 def printJ(self): return self._j class B(A): def __init__(self): super(B, self).__init__() self._j = 3 b = B() print b.printJ() # 打印如下 3 [Finished in 0.3s]
我们发现类B的实例b调用printJ方法时输出3, 这是因为B的构造器中重写了self._j的赋值。假始我们现在就是希望printJ方法打印父类A的构造器中的self_j值,即打印2,修改代码如下:
class A(object): __i = 1 def __init__(self): self.__j = 2 def printJ(self): return self.__j class B(A): def __init__(self): super(B, self).__init__() self.__j = 3 b = B() print b.printJ()
用__j 代替 _j, 这样B中就不能访问父类A中的私有变量__j,所以现在不是重写,而是重新定义了一个成员属性。可以通过下面的语句可以看出来确实是两个成员属性:
print b._A__j, b._B__j #打印 2 3 [Finished in 0.3s]
在class中定义__str__方法,这些在打印对象字符时更直观,更好阅读些
class Point(object): def __init__(self, x, y): self.x = x self.y = y p = Point(10, 11) print p
打印结果:
<__main__.Point object at 0x7f2f0f6b5c50>
如果在程序中记录这些的日志往往是无有多少可用信息可用,因为我们更多的关注对象中的各成员实际值,所以可以重新定义__str__方法来覆盖默认的行为:
class Point(object): def __init__(self, x, y): self.x = x self.y = y def __str__(self): return '{self.x},{self.y}'.format(self = self) p = Point(10, 11) print p
打印:
10,11
就像java里重写toString方法一样
用with管理操作资源的上下文环境
在一个比较典型的场景里,如数据库操作,我们操作connection时一般要正常关闭连接,而不管是正常退出还是异常退出。如下:
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()
上面我们用finally来保证conn的关闭。但是我们可以用with更优雅的完成这个工作。
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')
注意: 用with管理资源时,要定义__enter__和__exit__方法,它们分别在进入with和退出上下文时执行
运用generator
我们知道,运用list comprehensive可以对集合进行一定的运算变成另外一个集合对象,但是它会一下子生成所有元素并插入到新生成的列表集合中。如:
for e in [i * 2 for i in oneList]: pass
但是这样会比较消耗资源,而我们的目的的只是遍历新生成的列表集合进行相应的运算,所以这个时候用generator更适合:
for e in (i * 2 for i in oneList): pass
用(...) 代替 [....],就像用xrange代替range一样