Python 有哪些让你相见恨晚的技巧?
Posted Python学习与数据挖掘
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python 有哪些让你相见恨晚的技巧?相关的知识,希望对你有一定的参考价值。
欢迎关注 ,专注Python、数据分析、数据挖掘、好玩工具!
一、前言
经常有人会问:Python 有哪些让你相见恨晚的技巧?我今天准备把这个问题认真回答一下。我会先讨论什么是优美的代码;然后,我会给出一些我压箱底的好东西;最后,我会讨论怎么写出优美的代码。
二、什么是优美(优雅)的代码
什么是优美或优雅的代码实现呢?在Python里面,我们一般称之为Pythonic。Pythonic并没有一个确切的定义,一直以来都是只能意会,不能言传的东西。为了帮助新同学理解,我对Pythonic给出了明确的定义:所谓Pythonic,就是用Python的方式写出简洁优美的代码。
有了Pythonic以后,不同的工程师之间,也依然无法对优美的代码达成一致的意见。因为,美本身是一个主观感受,每个人对美的感受是不一样的。比如,有些人觉得汤唯更美,有些人觉得范冰冰最漂亮,还有些人居然喜欢AngelaBaby。而我,依然最喜欢刘涛。我在这篇文章中,会给出很多具体的例子,来说明怎样写代码是’美’的,由于美是一种主观感受,所以,这里的回答可能会引起大家的争议。
另外,在这篇文章中,我们只讨论优美的Python代码实现,并不讨论Python中存在的坑。我估计Python里面有很多坑大家都没有注意到,比如:
>>> a = 4.2
>>> b = 2.1
>>> print a+b == 6.3
False
三、优美的代码实现
在这一部分,我们会依次讨论一些美的代码。由于内容较多,所以,我进行了简单地分类,包括:
-
内置函数
-
Python中的一些小细节
-
充分使用数据结构的便利性
-
合理使用Python的高级并发工具
-
巧妙使用装饰器简化代码
-
Python中的设计模式
3.1 善用内置函数
enumerate类
enumerate是一个类,但是用起来却跟函数一样方便,为了表述方便,我们后面统称为函数。不使用enumerate可能是Python新手最容易被吐槽的地方了。enumerate其实非常简单,接收一个可迭代对象,返回index和可迭代对象中的元素的组合。
对于Python新手,推荐使用ipython(还有bpython和ptpython,感兴趣的同学也可以了解一下)交互式地测试各个函数的效果,并且,我们可以在函数后面输入一个问号,然后回车,就能够获得这个函数的帮助文档了。如下所示:
In [1]: enumerate?
Type: type
String Form:<type 'enumerate'>
Namespace: Python builtin
Docstring:
enumerate(iterable[, start]) -> iterator for index, value of iterable
Return an enumerate object. iterable must be another object that supports
iteration. The enumerate object yields pairs containing a count (from
start, which defaults to zero) and a value yielded by the iterable argument.
enumerate is useful for obtaining an indexed list:
(0, seq[0]), (1, seq[1]), (2, seq[2]), ...
关于enumerate的效果,我们一起来看一下,你就知道为什么不使用enumerate会被吐槽了。这是不使用enumerate的时候,打印列表中的元素和元素在列表中的位置代码:
from __future__ import print_function
L = [ i*i for i in range(5) ]
index = 0
for data in L:
index += 1
print(index, ':', data)
这是使用enumerate的Python代码:
from __future__ import print_function
L = [ i*i for i in range(5) ]
for index, data in enumerate(L):
print(index + 1, ':', data)
这是正确使用enumerate的姿势:
from __future__ import print_function
L = [ i*i for i in range(5) ]
for index, data in enumerate(L, 1):
print(index, ':', data)
去除import语句和列表的定义,实现同样的功能,不使用enumerate需要4行代码,使用enumerate只需要2行代码。如果想把代码写得简洁优美,那么,大家要时刻记住:在保证代码可读性的前提下,代码越少越好。显然,使用enumerate效果就好很多。
reversed
对Python熟悉的同学知道,Python中的列表支持切片操作,可以像L[::-1]这样去reverse列表。如下所示:
[1, 2, 3, 4]
>>> for item in L[::-1]:
... print(item)
...
4
3
2
1
与此同时,我们也可以使用内置的reversed函数,如下所示:
>>> for item in reversed(L):
... print(item)
...
4
3
2
1
我的观点是,L[::-1]不如使用reversed好,因为,L[::-1]是一个切片操作。我们看到这个代码的第一反应是序列切片,然后才是切片的效果是reverse列表。对于reversed函数,即使是刚接触Python的同学,也能够一眼看出来这个函数是要做什么事情。也就是说,实现同样的功能,L[::-1]比reversed多绕了一个弯。我们这个问题是如何写出优美的代码,而我认为,优美的代码就应该简洁、直接、少绕弯。
读者如果对我这里的解释表示怀疑的话,我表示理解。但是,我还是想劝你认可我的说法。因为我认为,不管我们使用代码还是文字,都是在表达某些东西。而我的表达能力,也是读研究生以后写论文锻炼出来的。就我目前比大多数人强的表达能力来说,我以我母校的荣誉保证,reversed确实比L[::-1]好。
any
在内置函数中,sort、sum、min和max是大家用的比较多的,也比较熟悉的。像any和all这种函数,是大家都知道,并且觉得很简单,但是使用的时候就想不起来的。我们来看一个具体的例子。
我们现在的需求是判断mysql中的一张表是否存在主键,有主键的情况,如下所示:
mysql> show index from t;
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| t | 0 | PRIMARY | 1 | id | A | 0 | NULL | NULL | | BTREE | | |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
1 row in set (0.00 sec)
我们再来看一个没有主键的例子,如下所示:
mysql> show index from t;
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| t | 0 | id | 1 | id | A | 0 | NULL | NULL | | BTREE | | |
| t | 1 | idx_age | 1 | age | A | 0 | NULL | NULL | YES | BTREE | | |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
2 rows in set (0.00 sec)
在这个没有主键的例子中,虽然没有显示定义主键,但是,它有一个非空的唯一索引。在InnoDB中,如果存在非空的唯一约束,那么,这一列将会被当作主键。综合前面两种情况的输出,我们知道,我们要判断一张表是否存在主键,我们不能通过是否存在一个key_name名为PRIMARY的索引来判断,而应该通过Non_unique为0和Null列不为YES来判断。说完了需求,我们来看一下具体的实现。使用pymysql连接数据库,数据库中的每一行,将会以元组的形式返回,如下所示:
(('t', 0, 'PRIMARY', 1, 'id', 'A', 0, None, None, '', 'BTREE', '', ''),)
也就是说,我们现在要遍历一个二维的元组,然后判断是否存在Non_unique为0,Null列不为YES的记录。详细了解了具体实现以后,我们写下了下面的代码:
def has_primary_key():
for row in rows:
if row[1] == 0 and row[9] != 'YES':
return True
return False
非常的简单,但是,如果我们使用any函数的话,代码将会更短。如下所示:
def has_primary_key():
return any(row[1] == 0 and row[9] != 'YES' for row in rows):
从这一节大家可以看到,即使内置函数这么简单的知识,我们也要充分掌握,灵活使用,才能够写出优美的代码。
3.2 Python中的小细节
这一节我们来看3个很小的知识点。
raise SystemExit
假设你现在要实现一个需求,在程序检测到某种错误的时候,打印错误信息,并退出程序。在Python中,我们可以是SystemExit,如下所示:
import sys
sys.stderr.write('It failed!\\n')
raise SystemExit(1)
但是,你其实可以直接这么用的:
raise SystemExit('It failed!')
后面的这个操作会直接将信息打印到标准错误输出,然后使用退出码为1来退出程序,以表示程序没有正常退出。
文件的x模式
大家应该知道,如果我们以w模式打开一个文件进行写入的话,文件的内容将会被我们覆盖掉。假设你现在有这样一个需求:写一个文件,如果该文件已经存在,则不写。实现方式也很简单,我们先判断一下文件是否存在,如果已经存在,则打印提示信息并跳过,否则,我们就以w模式打开文件,然后写入内容。如下所示:
>>> import os
>>> if not os.path.exists('somefile'):
... with open('somefile', 'wt') as f:
... f.write('Hello\\n')
... else:
... print('File already exists!')
...
File already exists!
如果我们使用x模式的话,代码能够好看很多,如下所示:
>>> with open('somefile', 'xt') as f:
... f.write('Hello\\n')
ConfigParser
上面两个例子知道的人可能比较多,这个例子知道的人可能就不多了。在大部分服务中,会将如数据库连接参数这样的配置,写到配置文件中,然后使用ConfigParser来管理。连接数据库的时候,我们可以读取配置参数,然后生成连接字符串。其实,ConfigParser本身就提供了生成连接字符串的功能,如下所示:
$cat db.conf
[DEFAULT]
conn_str = %(dbn)s://(%user)s:%(pw哪些 Python 库让你相见恨晚?