別再用print來調試啦:logging模塊超簡明指南

替換print?print怎麼了?html

print 多是全部學習Python語言的人第一個接觸的東西。它最主要的功能就是往控制檯 打印一段信息,像這樣:python

print 'Hello, logging!'

print也是絕大多數人用來調試本身的程序用的最多的東西,就像寫js使用 console.log 同樣那麼天然。不少剛剛開始學習Python的新手甚至有必定經驗的老手,都在使用print 來調試他們的代碼。小程序

好比這是一個我寫的輸出 斐波那契數列 的小程序,讓咱們來看看它的代碼:學習

# -*- coding: utf-8 -*-"""
A simple fibonacci program
"""import argparse

parser = argparse.ArgumentParser(description='I print fibonacci sequence')
parser.add_argument('-s', '--start', type=int, dest='start',
                    help='Start of the sequence', required=True)
parser.add_argument('-e', '--end', type=int, dest='end',
                    help='End of the sequence', required=True)def infinite_fib():
    a, b = 0, 1
    yield a    yield b    while True:        #print 'Before caculation: a, b = %s, %s' % (a, b)
        a, b = b, a + b        #print 'After caculation: a, b = %s, %s' % (a, b)
        yield bdef fib(start, end):
    for cur in infinite_fib():        #print 'cur: %s, start: %s, end: %s' % (cur, start, end)
        if cur > end:            return
        if cur >= start:            #print 'Returning result %s' % cur
            yield curdef main():
    args = parser.parse_args()    for n in fib(args.start, args.end):        print n,if __name__ == '__main__':
    main()

讓咱們來看看它工做的怎麼樣:ui

$ python fib.py  -s 1 -e 1001 
1 2 3 5 8 13 21 34 55 89
$ python fib.py  -s 100 -e 1000
144 233 377 610 987

沒有任何問題,程序正確的完成了它的功能。但等等, 程序裏面的那一堆被註釋掉的print語句是怎麼回事?spa

原來,這是我編寫這個小程序的過程當中,用來 調試(DEBUG) 的輸出信息,在我完成了這 個程序之後,我天然就把這些print給註釋掉了。讓咱們來看看若是把這個print語句打開後結果會怎麼樣?debug

$ python fib.py  -s 1 -e 100
cur: 0, start: 1, end: 100
cur: 1, start: 1, end: 100
Returning result 1
1 Before caculation: a, b = 0, 1
After caculation: a, b = 1, 1
cur: 1, 
start: 1, 
end: 100
... ...
... ...
(不可勝數的輸出信息)

如你所見,全部的計算過程都被打印出來了。調試

寫的時候加上print,提交代碼的時候還得記得把print語句刪掉/註釋掉,爲何咱們要忍受這樣的麻煩事呢? 讓咱們來介紹咱們的主角 logging ,它幾乎就是爲這種使用情景而生的。日誌


logging模塊是Python內置的日誌模塊,使用它能夠很是輕鬆的處理和管理日誌輸出。 logging模塊最簡單的用法,是直接使用basicConfig方法來對logging進行配置:code

import logging# 設置默認的level爲DEBUG# 設置log的格式logging.basicConfig(
    level=logging.DEBUG,
    format="[%(asctime)s] %(name)s:%(levelname)s: %(message)s")# 記錄loglogging.debug(...)
logging.info(...)
logging.warn(...)
logging.error(...)
logging.critical(...)

這樣配置完logging之後,而後使用``logging.debug``來替換全部的print語句就能夠了。 咱們會看到這樣的輸出:

[2014-03-18 15:17:45,216] root:cur: 0, start: 1, end: 100
[2014-03-18 15:17:45,216] root:DEBUG: cur: 1, start: 1, end: 100
[2014-03-18 15:17:45,216] root:DEBUG: Returning result 1
[2014-03-18 15:17:45,216] root:DEBUG: Before caculation: a, b = 0, 1
... 
...

上面說的basicConfig方法能夠知足你在絕大多數場景下的使用需求,可是basicConfig有一個 很大的缺點。

調用basicConfig實際上是給root logger添加了一個handler,這樣當你的程序和別的使用了 logging的第三方模塊一塊兒工做時,會影響第三方模塊的logger行爲。這是由logger的繼承特性決定的。

因此咱們須要使用真正的logger:

import logging
# 使用一個名字爲fib的logger
logger = logging.getLogger('fib')
# 設置logger的level爲DEBUG
logger.setLevel(logging.DEBUG)

# 建立一個輸出日誌到控制檯的StreamHandler
hdr = logging.StreamHandler()
formatter = logging.Formatter('[%(asctime)s] %(name)s:%(levelname)s: %(message)s')
hdr.setFormatter(formatter)

# 給logger添加上handler
logger.addHandler(hdr)

這樣再使用logger來進行日誌輸出就好了。不過這樣的壞處就是代碼量比basicConfig要大很多。 因此我建議若是是很是簡單的小腳本的話,直接使用basicConfig就能夠,若是是稍微大一些 項目,建議認真配置好logger。

動態控制腳本的全部輸出

使用了logging模塊之後,經過修改logger的log level,咱們就能夠方便的控制程序的輸出了。 好比咱們能夠爲咱們的斐波那契數列添加一個 -v 參數,來控制打印全部的調試信息。

# 添加接收一個verbose參數
parser.add_argument('-v', '--verbose', action='store_true', dest='verbose',
    help='Enable debug info')
    
    # 判斷verboseif args.verbose:
    logger.setLevel(logging.DEBUG)else:
    logger.setLevel(logging.ERROR)

這樣,默認狀況下,咱們的小程序是不會打印調試信息的,只有當傳入`-v/--verbose`的時候, 咱們纔會打印出額外的debug信息,就像這樣:

$ python fib.py  -s 1 -e 1001 
1 2 3 5 8 13 21 34 55 89
$ python fib.py  -s 1 -e 100 -v
[2014-03-18 15:17:45,216] fib:DEBUG: cur: 0, start: 1, end: 100
[2014-03-18 15:17:45,216] fib:DEBUG: cur: 1, start: 1, end: 100
[2014-03-18 15:17:45,216] fib:DEBUG: Returning result 1
[2014-03-18 15:17:45,216] fib:DEBUG: Before caculation: a, b = 0, 1
... ...

如你所見,使用了logging之後,何時須要打印DEBUG信息,何時須要關閉, 一切變的無比簡單。

因此,趕忙用logging替換掉你的腳本里的print吧!

延伸閱讀

以上這些只是介紹了logging模塊最簡單的一些功能,做爲print的替代品來使用,logging 模塊還有不少很是強大好用的功能,好比從文件讀取配置、各類各樣的Handlers等等。 建議閱讀一下logging的官方文檔:

  1. logging Logging facility for Python

  2. Logging HOWTO

最後附上使用logging模塊的斐波那契數列程序完整代碼:

# -*- coding: utf-8 -*-
"""
A simple fibonacci program
"""
import argparse

parser = argparse.ArgumentParser(description='I print fibonacci sequence')
parser.add_argument('-s', '--start',
                    type=int,
                    dest='start',
                    help='Start of the sequence',
                    required=True)
parser.add_argument('-e', '--end',
                    type=int,
                    dest='end',
                    help='End of the sequence',
                    required=True)
parser.add_argument('-v', '--verbose',
                    action='store_true',
                    dest='verbose',
                    help='Enable debug info')

import logging

logger = logging.getLogger('fib')
logger.setLevel(logging.DEBUG)

hdr = logging.StreamHandler()
formatter = logging.Formatter(
    '[%(asctime)s] %(name)s:%(levelname)s: %(message)s')
hdr.setFormatter(formatter)

logger.addHandler(hdr)


def infinite_fib():
    a, b = 0, 1
    yield a
    yield b
    while True:
        logger.debug('Before caculation: a, b = %s, %s' % (a, b))
        a, b = b, a + b
        logger.debug('After caculation: a, b = %s, %s' % (a, b))
        yield b


def fib(start, end):
    for cur in infinite_fib():
        logger.debug('cur: %s, start: %s, end: %s' % (cur, start, end))
        if cur > end:
            return
        if cur >= start:
            logger.debug('Returning result %s' % cur)
            yield cur


def main():
    args = parser.parse_args()
    if args.verbose:
        logger.setLevel(logging.DEBUG)
    else:
        logger.setLevel(logging.ERROR)

    for n in fib(args.start, args.end):
        print n,


if __name__ == '__main__':
    main()
相關文章
相關標籤/搜索