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)s@%(host)s:%(port)s/%(db)s
    dbn = mysql
    user = root
    pw = root
    host = localhost
    port = 3306
    db = test

这里给出了几个Python中的小细节,可能很多人会觉得没啥用,又或者大家其实已经知道了。但是,我还是把这一节放上来了,只要对一个人有用,那么,这就是有意义的。

3.3 合理使用数据结构

这一节中,我们会讨论Python中的部分数据结构的用法,可能是对大家最有用的章节。

字典的get可以传递默认值
很多人是这么给参数赋默认值的:

    port = kwargs.get('port')
    if  port is None:
        port = 3306

其实,我们完全不用这么麻烦,因为,字典的get方法支持提供默认参数,在字典没有值的情况下,将会返回用户提供的默认参数,所以,优美的代码应该是这样的:

port = kwargs.get('port', 3306)

我们再来看一个类似的例子,获取元素且删除:

    L = [1, 2, 3, 4]
    last = L[-1]
    L.pop()

这里,我们又多此一举了。因此,在调用L.pop()函数的时候,本身就会返回给我们需要pop的数据。也就是说,我们可以这样:

defaultdict & Counter
我们来看两个不是Python内置的数据类型,而是Python标准库里面的例子。

假设我们的字典中的value是一个list。大家知道,list也是元素的集合。所以,我们会见到很多下面这样的代码,先判断key是否已经存在,如果不存在,则新建一个list并赋值给key,如果已经存在,则调用list的append方法,将值添加进去。

        d = {}
        for key, value in pairs:
            if key not in d:
                d[key] = []
            d[key].append(value)

如果我们知道defaultdict,那就不用这么麻烦了,如下所示:

        d = defaultdict(list)
        for key, value in pairs:
            d[key].append(value)

我们接着上面的defaultdit的例子来讨论。现在有一个需求,需要统计一个文件中,每个单词出现的次数。很多新同学拿到这个题目,首先想到的是使用字典,所以,写出来下面这样的代码:

    d = {}
    with open('/etc/passwd') as f:
        for line in f:
            for word in line.strip().split(':'):
                if word not in d:
                    d[word] = 1
                else:
                    d[word] += 1

如果我们使用前面介绍的defaultdict,代码能够减少3行,会被认为更加Pythonic。如下所示:

    d = defaultdict(int)
    with open('/etc/passwd') as f:
        for line in f:
            for word in line.strip().split(':'):
                d[word] += 1

对于这个问题,其实还有一个更好的解决办法,使用collections中的Counter。如下:

    word_counts = Counter()
    with open('/etc/passwd') as f:
        for line in f:
            word_counts.update(line.strip().split(':'))

可以看到,使用Counter以后,我们的代码更加短小了。我们先把代码重8行重构到5行,然后又重构到4行。记住我前面给出的实践方法:要想把代码写得优美,在保证可读性的前提下,代码越短越好。对于这个问题,使用Counter还有其他的一些理由,那就是其他相关的需求。比如,现在还有第二个需求,打印出现次数最多的三个单词。如果我们使用字典,那么,我们需要这样:

    result = sorted(zip(d.values(), d.keys()), reverse=True)[:3]
    for val, key in result:
        print(key, ':', val)

使用Counter就简单了,因为Counter直接就为我们提供了相应的函数,如下所示:

    for key, val in (word_counts.most_common(3)):
        print(key, ':', val)

是不是代码更短,看起来更加清晰呢。而且,统计每个单词出现的次数和出现次数最多的单词,这两个需求相关性实在是太强了,几乎会同时出现。所以,我们使用了Counter模块和该模块的most_common方法。如果Counter没有提供这个方法,那才是要被吐槽的!

这个例子就充分说明了,善用标准库的重要性。我们再来看一个标准库的例子。

nametuple
我们要写一个监控系统,该系统要监控主机的方方面面。当然,也包括磁盘相关的监控。我们可以从/proc/diskstats中获取磁盘的详细信息。/proc/diskstats 的内容像下面这样

    """
    https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats

    What:       /proc/diskstats

            The /proc/diskstats file displays the I/O statistics
            of block devices. Each line contains the following 14
            fields:
             1 - major number
             2 - minor mumber
             3 - device name
             4 - reads completed successfully
             5 - reads merged
             6 - sectors read
             7 - time spent reading (ms)
             8 - writes completed
             9 - writes merged
            10 - sectors written
            11 - time spent writing (ms)
            12 - I/Os currently in progress
            13 - time spent doing I/Os (ms)
            14 - weighted time spent doing I/Os (ms)
    """
    $ cat /proc/diskstats
    254       0 vda 24471 251 818318 33200 37026 64382 2289256 394516 0 24868 427704
    254       1 vda1 24143 229 815530 33156 36072 64382 2289256 394428 0 24748 427572
    254      32 vdc 614 0 22180 408 51203 2822 1857922 1051716 0 40792 1052064

/proc/diskstats文件有很多列,如果我们使用下标访问的话,肯定需要借助我们的手指头。并且,也不一定能数清楚。就算你算术特别厉害,能够轻易地数清楚,如果下一个人要来修改你写的代码,对他来说,将会是一个噩梦。

作为一个有追求的程序员,我们当然要寻找更好的办法。对于这个问题,我们可以使用Python中的命名元组,也就是collections中的namedtuple。我们定义命名元组:

    DiskDevice = collections.namedtuple('DiskDevice', 'major_number minor_number device_name read_count read_merged_count'
                                                  ' read_sections time_spent_reading write_count write_merged_count '
                                                  'write_sections time_spent_write io_requests time_spent_doing_io'
                                                  ' weighted_time_spent_doing_io')

有了命名元组以后,如果我们要返回某个磁盘的请求数据,就返回一个命名元组。调用者通过该命名元组,就能够通过属性的方式,而不是下标的方式访问各个字段。获取磁盘监控的代码如下:

    def get_disk_info(disk_name):
        with open("/proc/diskstats") as f:
            for line in f:
                if line.split()[2] == disk_name:
                    #返回给调用者的是一个命名元祖(namedtuple)
                    return DiskDevice(*(line.split()))

3.4 使用高级并发工具

数据结构先讲到这边,我们来看一个并发的例子。即生产者和消费者模型。分别创建生产者和消费者,生产者向队列中放东西,消费者从队列中取东西。创建一个锁来保证线程间操作的互斥性,当队列满的时候,生产者进入等待状态,当队列空的时候,消费者进入等待状态。下面是一个简单的实现Producer-consumer problem in Python

  from threading import Thread, Condition
    import time
    import random

    queue = []
    MAX_NUM = 10
    condition = Condition()

    class ProducerThread(Thread):
        def run(self):
            nums = range(5)
            global queue
            while True:
                condition.acquire()
                if len(queue) == MAX_NUM:
                    print "Queue full, producer is waiting"
                    condition.wait()
                    print "Space in queue, Consumer notified the producer"
                num = random.choice(nums)
                queue.append(num)
                print "Produced", num
                condition.notify()
                condition.release()
                time.sleep(random.random())


    class ConsumerThread(Thread):
        def run(self):
            global queue
            while True:
                condition.acquire()
                if not queue:
                    print "Nothing in queue, consumer is waiting"
                    condition.wait()
                    print "Producer added something to queue and notified the consumer"
                num = queue.pop(0)
                print "Consumed", num
                condition.notify()
                condition.release()
                time.sleep(random.random())


    ProducerThread().start()
    ConsumerThread().start()

对于这一类同步问题,其实,我们应该直接使用Queue。Queue提供了线程安全的队列,特别适合用来解决生产者和消费者问题。这里我再强调一下,Queue本身是线程安全的,而且支持阻塞读、阻塞写。如下所示:

   from threading import Thread
    import time
    import random
    from Queue import Queue

    queue = Queue(10)

    class ProducerThread(Thread):
        def run(self):
            nums = range(5)
            global queue
            while True:
                num = random.choice(nums)
                queue.put(num)
                print "Produced", num
                time.sleep(random.random())


    class ConsumerThread(Thread):
        def run(self):
            global queue
            while True:
                num = queue.get()
                queue.task_done()
                print "Consumed", num
                time.sleep(random.random())


    ProducerThread().start()
    ConsumerThread().start()

可以看到,使用Queue以后,代码量少了很多,可维护性也强了不少。当然了,我这里只是举了一个特别简单的例子,只是想说明,我们应该使用高级的并发工具。

我们再来看一个并发的例子。很多时候,我们要用并发编程,并不用自己手动启动一个线程或进程,完全可以使用Python提供的并发工具,如下所示:

内置的map是单线程运行的,如果涉及到网络请求或者大量的cpu计算,则速度相对会慢很多,因此, 出现了并发的map,如下所示:

    import requests
    from multiprocessing import Pool
    
    def get_website_data(url):
        r = requests.get(url)
        return r.url
    
    def main():
        urls = ['http://www.google.com',
                'http://www.baidu.com',
                'http://www.163.com']
        pool = Pool(2)
        print pool.map(get_website_data, urls)
    
    main()

为了与线程兼容,该模块还提供了multiprocessing.dummy,用以提供线程池的实现,如下所示:

from multiprocessing.dummy import Pool

使用Pool,我们可以快速的在线程池和进程池之间来回切换,最重要的是,使用高级的并发工具不那么容易出错。

3.5 使用装饰器

装饰器可能是Python面试中被问的最多的知识了。之所以如此频繁的出现,是因为,装饰器确实是个好东西。举个例子,我们有两个模块,A模块要发消息给B模块,B模块检查A模块发送过来的参数,没有问题就进行处理,对于检查参数这个操作,如果我们使用装饰器的话,代码将会是下面这样:

 import inspect
    import functools

    def check_args(parameters):
        """check parameters of action"""
        def decorated(f):
            """decorator"""
            @functools.wraps
            def wrapper(*args, **kwargs):
                """wrapper"""
                func_args = inspect.getcallargs(f, *args, **kwargs)
                msg = func_args.get('msg')

                for item in parameters:
                    if msg.body_dict.get(item) is None:
                        return False, "check failed, %s is not found" % item

                return f(*args, **kwargs)
            return wrapper
        return decorated

使用的时候:

    class AsyncMsgHandler(MsgHandler):
        @check.check_args(['ContainerIdentifier', 'MonitorSecretKey', "InstanceID", "UUID"])
        def init_container(self, msg):
            pass

这样很多好处,例如,我们不用为不同的消息,编写不同的参数检查函数。我们将参数检查和业务逻辑完全分离,使得代码更加简洁明了。当然,装饰器还有各种用处,我就不多说了。

3.6 Python中的设计模式

设计模式本来是与编程语言不相关的,属于程序架构的范畴。但是,有些设计模式,在Python中,完全可以使用Python的一些特性,实现得更加简洁优美。

单例模式
模式是最简单也比较常用的一种模式,所谓单例模式,就是要保证系统中一个类只有一个实例。我们来看一个典型的单例实现:

  public class Singleton {
        private static Singleton instance;
        private Singleton (){}
        public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
        }
    }

上面这段java代码大家可以轻易地转换成Python的实现,然而,并不Pythonic。给大家一种思路,提供了一个Borg类,只要继承该类,就会成为单例。

    class Borg:
        _shared_state = {}
        def __init__(self):
            self.__dict__ = self.__shared_state

这个实现的思路是"实例的唯一性并不是重要的,我们应该关注的是实例的状态,只要所有的实例共享状态,行为一致,那就达到了单例的目的"。通过Borg模式,可以创建任意数量的实例,但因为它们共享状态,从而保证了行为一致。

对Python的模块熟悉的人会知道,在Python中,模块只初始化一次,所有变量归属于某个模块,import机制是线程安全的。所以,模块本身是天然的单例实现。因此,我们可以借助模块来实现单例模式。

下面这个例子创建了一个函数,令其返回含有货币汇率的字典,这个函数可以被多次调用,但是,大部分情况下,汇率数据只获取一次就够了,无须每次调用时都获取一遍。

    def get():
        if not get.rates:
            _URL = 'http://www.bankofcanada.ca/stats/assets/csv/fx-seven-day.csv'
            with urllib.request.urlopen(_URL) as file:
                for line in file:
                    # get.rates[key] = float(data)
                    pass

        return get.rates

    get.rates = {}

在这段代码中,我们创建了名为rates的字典,用于保存私有数据,并将该字典设置成Rates.get()函数的属性。第一次执行公开的get()函数时,会下载全新的汇率数据;其他时候,只需要把最近下载的那份数据返回就行了。尽管没有引入类,但我们依然把汇率数据做成了"单例数据值"。大家可以看到,在Python中,单例可以直接使用模块来实现,非常的简单。

工厂模式
我们再来看另外一个非常常用的设计模式,工厂模式。下面这段代码是C++编程思想 (豆瓣)中的工厂模式:

    class Shape
        class Circle: public Shape
        class Square: public Shape

        Shape* Shape::factory(const string &type)
        {
            if (type == "Circle")
                {return new Circle;}
            if (type == "Square")
                {return new Square;}
        }

说实话,实现得非常好,可读性很强。我们再看一下,同样的需求,在Python中如何实现:

        class Shape:pass
        class Circle(Shape):pass
        class Square(Shape):pass

        for name in ["Circle", "Square"]:
            cls = globals()[name]
            obj = cls()

要理解Python中的工厂模式,关键是要知道,Python中的类也是可调用的对象。而且,我们import以后,存在于当前的命名空间中,所以,我们可以先通过名字获取到"类",再用类构造出对象。比较这里的C++实现和Python实现可以发现,当我们需要新增一个图形的时候,C++的实现需要去修改Shape::factory函数,Python就不需要这么麻烦,也就是说,Python版本的工厂模式比C++版本的工厂模式少了一个需要维护的函数。从这个角度来说,Python程序员是幸福的。

四、如何写出优美(优雅)的代码

关于这个问题,我想说的还有很多,然而,我编辑了好几天也没有编辑完。总是有个结束的时候,那就到此为止吧。

关于如何能够写出Pythonic的代码,我的观点是:不管用什么语言,你都应该努力写出简洁优美的代码。如果不能,那我推荐你看看《重构》和《代码整洁之道》。虽然这两本书使用的是java语言,但是,并不影响作者要传递的思想。

此外,我也有一些经验传授给大家,希望能够帮助新同学快速地写出还不错的代码。

像写报纸一样写代码
准确无歧义
完整无废话
注意排版以引导读者
注意标点符号以帮助读者
保证可读性的前提下,代码尽可能短小

其中,“保证代码可读性的前提下,代码尽可能短小”,这个我已经反复强调,相信大家已经有点感觉了。我们来看一个"注意标点符号以帮助读者",在Python里面,空行是会被忽略的,也就说,有没有空行,有多少空行,对Python来说都是一样的,但是,对程序员来说可就不一样了,我们来看一个例子。随机生成1000个0~999之间的整数,然后求他们的和。

这里是没有空行的例子:

    import random
    def sum_num(num, min_num, max_num):
        i = 0
        data = []
        for i in range(num):
            data.append(random.randint(min_num, max_num))
        total = 0
        for item in data:
            total += item
        return total
    if __name__ == '__main__':
        print sum_num(1000, 0, 1000)

这里是有空行的例子:

   import random
    
    
    def sum_num(num, min_num, max_num):
        i = 0
        data = []
        for i in range(num):
            data.append(random.randint(min_num, max_num))
    
        total = 0
        for item in data:
            total += item
    
        return total
    
    
    if __name__ == '__main__':
        print sum_num(1000, 0, 1000)

可以看到,有空行的代码,明显看起来更加舒适愉悦。在大家用汉语写作文的时候,老师一直教导我们要合理的使用标点符号,合理的分段。其实,写代码和写文章是一样的。在代码中,大家可以这样想象:换行是逗号,空一行是句号,空两行是分段。至于逗号,由于我们总是在一行中写一条语句,所以,逗号是可以忽略的,如果你在一行中写了多条语句,就好比在写作文的时候没有正确的使用逗号,也让人难以理解。如果你从来不空行,所有代码纠缠在一起,就好比没有句号,让人读起来很累,同理,不分段也不是 好习惯。

参考链接:https://www.zhihu.com/question/48755767/answer/130286487

在这篇回答中,我给出了很多Python优雅实现的例子,然后,讨论了如何才能写出优美的代码,希望对大家有帮助。


技术交流

欢迎转载、收藏、有所收获点赞支持一下!

在这里插入图片描述
目前开通了技术交流群,群友超过2000人,添加方式如下:

如下方式均可,添加时最好方式为:来源+兴趣方向,方便找到志同道合的朋友

  • 方式一、发送如下图片至微信,进行长按识别,回复加群;
  • 方式二、直接添加小助手微信号:pythoner666,备注:来自CSDN
  • 方式三、微信搜索公众号:Python学习与数据挖掘,后台回复:加群

在这里插入图片描述

  • 14
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值