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

三、优美的代码实现

在这一部分,我们会依次讨论一些美的代码。由于内容较多,所以,我进行了简单地分类,包括:

  1. 内置函数

  2. Python中的一些小细节

  3. 充分使用数据结构的便利性

  4. 合理使用Python的高级并发工具

  5. 巧妙使用装饰器简化代码

  6. 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 库让你相见恨晚?

Python哪些让人相见恨晚的技巧

有什么让你相见恨晚的 iPhone 使用技巧?

IntelliJ IDEA 中有什么让你相见恨晚的技巧?

让你相见恨晚的Excel转换成PDF技巧

学会这5个Excel技巧,让你拒绝加班