我的python學習–第十二天(二)

Python異常處理

  Python的異常處理能力是很強大的,可向用戶準確回饋出錯資訊。在Python中,異常也是對象,可對它進行操作。

所有異常都是基類Exception的成員,所有異常都從基類Exception繼承,而且都在exceptions模組中定義,

Python自動將所有異常名稱放在內建命名空間中,所以程式不必導入exceptions模組即可使用異常。

一、格式

try:      block                  except 異常類型:      block                 finally:                                      block

該種異常處理語法的規則是:

  • 執行try下的語句,如果引發異常,則執行過程會跳到第一個except語句。
  • 如果第一個except中定義的異常與引發的異常匹配,則執行該except中的語句。
  • 如果引發的異常不匹配第一個except,則會搜索第二個except,允許編寫的except數量沒有限制。
  • 如果所有的except都不匹配,則異常會傳遞到下一個調用本程式碼的最高層try程式碼中。
  • 不管上面執行的怎麼樣,都要執行finally下面的內容。

示例程式碼:

try:      f = open(「file.txt」,」r」)  except IOError, e:      # 捕獲到的IOError錯誤的詳細原因會被放置在對象e中,然後運行該異常的except程式碼塊      print e

可以使用Exception來捕獲所有的異常,所有的異常資訊都收來了,簡單省心

try:      f = open(「file.txt」,」r」)  except Exception,e:    # Exception是所有異常類的基類,所有類型的錯誤資訊都會輸入到e中      print e

常見異常類型

  • AttributeError     試圖訪問一個對象沒有的樹形,比如foo.x,但foo沒有屬性x
  • IOError         輸入輸出異常;基本是無法打開文件錯誤
  • ImportError      無法引入模組或者包;基本上是路徑問題或者名稱錯誤
  • IndentationError   語法錯誤;程式碼沒有正確的對齊
  • IndexError:       下標索引超出序列邊界,比如當x只有三個元素,卻試圖訪問x[5]
  • KeyError         試圖訪問字典里不存在的鍵              
  • NameError        使用一個還未賦值的變數
  • SyntaxError       程式碼非法,
  • TypeError        傳入對象類型與要求的不符合
  • ValueError       傳給函數的參數類型不正確,比如給int()函數傳入字元串形

二、traceback獲取詳細的異常資訊

1:傳統方式的異常處理

In [1]: try:     ...:     1/0     ...: except Exception,e:     ...:     print e     ...:       integer division or modulo by zero               # 只顯示簡單的錯誤資訊

2:加入了traceback之後的異常處理

In [1]: import traceback    In [2]: try:     ...:     1/0     ...: except Exception:     ...:     traceback.print_exc()                 # 列印出詳細的錯誤資訊     ...:       Traceback (most recent call last):    File "<ipython-input-2-7989d926ba7a>", line 2, in <module>      1/0  ZeroDivisionError: integer division or modulo by zero

3:traceback.print_exc() vs traceback.format_exc()

  format_exc():返回字元串,可以結合logging模組使用

    logging.getLogger().error("Get users list error: %s" % traceback.format_exc())

  print_exc():直接給列印出來。也可以接受file參數直接寫入到一個文件

    traceback.print_exc()                       # 列印到螢幕

    traceback.print_exc(file=open('tb.txt','w+'))       # 錯誤資訊重定向到文件

三、手動觸發異常

  在Python中,除了程式自身錯誤引發的異常外,也可以根據自己需要手工引發異常,最簡單的形式就是輸入關鍵

字raise,後跟要引發的異常的名稱。

  raise語法格式如下:

    raise[Exception[, args [, traceback]]]

  語句中Exception是異常的類型(例如,NameError)參數是一個異常參數值。該參數是可選的,如果不提供,異

常的參數是"None"。

定義一個異常:

In [1]: import traceback    In [2]: try:     ...:     print 'hello world'     ...:     raise Exception('just a test')      # 自己定義一個異常     ...: except Exception:     ...:     traceback.print_exc()     ...:       hello world  Traceback (most recent call last):    File "<ipython-input-2-32f7ee25cfcc>", line 3, in <module>      raise Exception('just a test')  Exception: just a test

生產中自定義異常的方式:直接return 錯誤錯誤編號和資訊

try:      ... ...      if role != 0:          return json.dumps({'code':1,'errmsg':'you are not admin'})      ... ...  except:      logging.getLogger().error("select  Cabinet list error: %s" % traceback.format_exc())      return json.dumps({'code': 1, 'errmsg': 'select  Cabinet list error'})

logging模組

一、概述

  在實際項目中,需要對一些數據進行日誌記錄,並將日誌記錄到不同的存儲單元中,例如資料庫,文本,或者推送到圖形化介面中,當需要時發現自己實現一個日誌庫其實是要很大的代價,因此,第三方的日誌庫上進行訂製化處理 正文內容是對logging的理解和使用方式,非常方便

1:四個主要類,使用官方文檔中的概括:

  • logger       提供了應用程式可以直接使用的介面;
  • handler      將(logger創建的)日誌記錄發送到合適的目的輸出;
  • filter       提供了細度設備來決定輸出哪條日誌記錄;用處不太大
  • formatter     決定日誌記錄的最終輸出格式

2:模組級函數

  • logging.getLogger([name])       # 返回一個logger對象,如果沒有指定名字將返回root logger,最常用
  • logging.basicConfig():         # 給logger對象的配置管理函數,不常用   
  • logging.debug()、logging.info()、logging.warning()、logging.error()、logging.critical(): # logger的日誌級別

二、logging工作流演示

#coding:utf-8  import logging    # 創建一個logger命名為mylogger(可以是任意字元串), %(name)s可調用這個名字  logger = logging.getLogger('mylogger')  logger.setLevel(logging.DEBUG)    # 創建一個handler,用於寫入日誌文件,只輸出debug級別以上的日誌  fh = logging.FileHandler('test.log')  fh.setLevel(logging.DEBUG)    # 再創建一個handler,用於輸出到控制台  ch = logging.StreamHandler()  ch.setLevel(logging.DEBUG)    # 定義handler的輸出格式  formatter = logging.Formatter('%(asctime)s - %(name)s - %(filename)s- %(levelname)s - %(message)s')  fh.setFormatter(formatter)  ch.setFormatter(formatter)    # 給logger添加handler  logger.addHandler(fh)  logger.addHandler(ch)    # 記錄兩條日誌  logger.info('foorbar')    logger.debug('just a test ')

運行結果:

[root@yaoliang day_12]# python test.py   2016-10-17 17:26:10,111 - mylogger - test.py- INFO - foorbar  2016-10-17 17:26:10,113 - mylogger - test.py- DEBUG - just a test

三、logging模組的api

1:logging.getLogger([name])

  返回一個logger實例,如果沒有指定name,返回root logger。只要name相同,返回的logger實例都是同一個而且只有一個,即name和logger實例是一一對應的。這意味著,無需把logger實例在各個模組中傳遞。只要知道name,就能得到同一個logger實例

2:logger.setLevel(lvl):設置logger記錄日誌的級別

level有以下幾個級別:

  NOTSET < DEBUG < INFO < WARNING < ERROR < CRITICA

  如果把logger的級別設置為INFO,那麼小於INFO級別的日誌都不輸出,大於等於INFO級別的日誌都輸出。也就意味著同一個logger實例,如果多個地方調用,會出現很多重複的日誌

3:logger.addHandler(hd):logger僱傭handler來幫它處理日誌

  handler對象負責發送相關的資訊到指定目的地。Python的日誌系統有多種Handler可以使用。有些Handler可以把資訊輸出到控制台,有些Logger可以把資訊輸出到文件,還有些 Handler可以把資訊發送到網路上。如果覺得不夠用,還可以編寫自己的Handler。可以通過addHandler()方法添加多個多handler

handler主要有以下幾種:

(常用)

  • logging.StreamHandler:              # 日誌輸出到流即控制台,可以是sys.stderr、sys.stdout
  • logging.FileHandler:                # 日誌輸出到文件
  • logging.handlers.RotatingFileHandler:    # 日誌輸出到文件,並按照設定的日誌文件大小切割
  • logging.handlers.TimedRotatingFileHandler  # 日誌輸出到文件,並按設定的時間切割日誌文件

(不常用) 

  • logging.handlers.SocketHandler:         # 遠程輸出日誌到TCP/IP sockets
  • logging.handlers.DatagramHandler:       # 遠程輸出日誌到UDP sockets
  • logging.handlers.SMTPHandler:          # 遠程輸出日誌到郵件地址
  • logging.handlers.SysLogHandler:         # 日誌輸出到syslog
  • logging.handlers.NTEventLogHandler:      # 遠程輸出日誌到Windows NT/2000/XP的事件日誌
  • logging.handlers.MemoryHandler:         # 日誌輸出到記憶體中的制定buffer

  由於StreamHandler和FileHandler是常用的日誌處理方式,所以直接包含在logging模組中,而其他方式則包含在logging.handlers模組中,

handle常見調用

  • Handler.setLevel(lel)               # 指定被處理的資訊級別,低於lel級別的資訊將被忽略
  • Handler.setFormatter()              # 給這個handler選擇一個格式
  • Handler.addFilter(filter)            # 新增或刪除一個filter對象
  • Handler.removeFilter(filter)          # 新增或刪除一個filter對象

logging生產環境的使用方法:將其封裝為函數

#/usr/bin/env python  #coding:utf-8  import logging,logging.handlers    def WriteLog(log_name):      log_filename = "/tmp/test.log"      log_level = logging.DEBUG         # 日誌級別      format = logging.Formatter('%(asctime)s %(filename)s [line:%(lineno)2d]-%(funcName)s  %(levelname)s %(message)s')       # 日誌格式      handler = logging.handlers.RotatingFileHandler(log_filename, mode='a', maxBytes=10*1024*1024, backupCount=5)        # 日誌輸出到文件,文件最大10M,最多5個      handler.setFormatter(format)        logger = logging.getLogger(log_name)      logger.setLevel(log_level)            if not logger.handlers:        # 每調用一次就會添加一個logger.handler,每次就額外多列印一次日誌,if判斷使其只調用一次          logger.addHandler(handler)                return logger         # 函數最終將實例化的logger對象返回,後面直接調用即可    if __name__ == "__main__":      WriteLog('api').info('123')         # 模組內部直接調用函數。等價下面兩行      # 下面的方法不推薦      # writelog = WriteLog('api')      # writelog.info('123')

4、logging.basicConfig([**kwargs]):載入logger的各項配置參數,不好用

# coding:utf-8  import logging  logging.basicConfig(level=logging.DEBUG,   # 輸出debug及其級別更高級別的日誌             format='%(asctime)s %(filename)s [line:%(lineno)d] %(levelname)s %(message)s',             datefmt='%d %b %Y %H:%M:%S',             filename='myapp.log',           # 日誌文件輸出的文件地址,不寫默認列印到桌面             filemode='w')    logging.debug("this is debug message")  logging.info("this is info message")  logging.warning("this is warning message")

結果

[root@yaoliang day_12]# tail myapp.log   17 Oct 2016 17:42:48 test2.py [line:9] DEBUG this is debug message  17 Oct 2016 17:42:48 test2.py [line:10] INFO this is info message  17 Oct 2016 17:42:48 test2.py [line:11] WARNING this is warning message

關於logging.basicConfig函數的常用配置:

filename:                # 指定日誌文件名

filemode:                # 和file函數意義相同,指定日誌文件的打開模式,'w'或'a'

datefmt:                # 指定時間格式,同time.strftime()

level:                  # 設置日誌級別,默認為logging.WARNING,即warning及級別更高日誌才輸出

stream                  # 指定將日誌的輸出流,可以指定輸出到sys.stderr,sys.stdout或者文件,

                       默認輸出到sys.stderr,當stream和filename同時指定時,stream被忽略

format                  # 指定輸出的格式和內容,format可以輸出很多有用資訊

  • %(name)s:        # 列印logger名,默認為root
  • %(levelno)s:       # 列印日誌級別的數值
  • %(levelname)s:     # 列印日誌級別名稱
  • %(pathname)s:      # 列印當前執行程式的路徑,其實就是sys.argv[0]
  • %(filename)s:      # 列印當前執行程式名
  • %(funcName)s:      # 列印日誌的當前函數
  • %(lineno)d:       # 列印日誌的當前行號
  • %(asctime)s:      # 列印日誌的時間
  • %(message)s:      # 列印日誌資訊
  • %(thread)d:       # 列印執行緒ID
  • %(threadName)s:    # 列印執行緒名稱
  • %(process)d:      # 列印進程ID

5、logging.config模組通過配置文件的方式,載入logger的參數,最好用的方式

[root@yaoliang day_12]# cat logger.conf  # 定義logger模組,root是父類,必需存在的,其它的是自定義。  # logging.getLogger(NAME)就相當於向logging模組註冊了實例化了  # name 中用 . 表示 log 的繼承關係  [loggers]     keys=root,example01,example02  # [logger_xxxx] logger_模組名稱  # level     級別,級別有DEBUG、INFO、WARNING、ERROR、CRITICAL  # handlers  處理類,可以有多個,用逗號分開  # qualname  logger名稱,應用程式通過 logging.getLogger獲取。對於不能獲取的名稱,則記錄到root模組。  # propagate 是否繼承父類的log資訊,0:否 1:是  [logger_root]  level=DEBUG  handlers=hand01,hand02  [logger_example01]  handlers=hand01,hand02  qualname=example01  propagate=0  [logger_example02]  handlers=hand01,hand03  qualname=example02  propagate=0  # [handler_xxxx]  # class handler類名  # level 日誌級別  # formatter,上面定義的formatter  # args handler初始化函數參數  [handlers]  keys=hand01,hand02,hand03    [handler_hand01]  class=StreamHandler  level=INFO  formatter=form02  args=(sys.stderr,)    [handler_hand02]  class=FileHandler  level=DEBUG  formatter=form01  args=('myapp.log', 'a')  [handler_hand03]  class=handlers.RotatingFileHandler  level=INFO  formatter=form02  args=('myapp.log', 'a', 10*1024*1024, 5)  # 日誌格式  [formatters]  keys=form01,form02  [formatter_form01]  format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s  datefmt=%a, %d %b %Y %H:%M:%S  [formatter_form02]  format=%(asctime)s%(name)-12s: %(levelname)-8s %(message)s  datefmt=%a, %d %b %Y %H:%M:%S

調用

import logging  import logging.config    logging.config.fileConfig("logger.conf")  logger = logging.getLogger("example01")  logger.debug('This is debug message')  logger.info('This is info message')  logger.warning('This is warning message')

生產環境中的調用方法:通過函數

import logging,  import  logging.config    def write_log(loggername):      work_dir = os.path.dirname(os.path.realpath(__file__))      log_conf= os.path.join(work_dir, 'conf/logger.conf')      logging.config.fileConfig(log_conf)      logger = logging.getLogger(loggername)      return logger

四、關於root logger以及logger的父子關係

如何得到root logger?

  root logger是默認的logger如果不創建logger實例, 直接調用logging.debug()、logging.info()logging.warning(),logging.error()、logging.critical()這些函數,

那麼使用的logger就是 root logger, 它可以自動創建,也是單實例的。

root logger的日誌級別?

  root logger默認的level是logging.WARNING

如何表示父子關係?

  logger的name的命名方式可以表示logger之間的父子關係. 比如:

parent_logger = logging.getLogger('foo')

child_logger = logging.getLogger('foo.bar')

什麼是effective level?

  logger有一個概念,叫effective level。 如果一個logger沒有顯示地設置level,那麼它就

用父親的level。如果父親也沒有顯示地設置level, 就用父親的父親的level,以此推….

最後到達root logger,一定設置過level。默認為logging.WARNING

child loggers得到消息後,既把消息分發給它的handler處理,也會傳遞給所有祖先logger處理,

示例:

# coding:utf-8  import logging    # 設置root logger,祖先  r = logging.getLogger()  ch = logging.StreamHandler()  ch.setLevel(logging.DEBUG)  formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')  ch.setFormatter(formatter)  r.addHandler(ch)    # 創建一個logger作為父親  p = logging.getLogger('foo')  p.setLevel(logging.DEBUG)  ch = logging.StreamHandler()  ch.setLevel(logging.DEBUG)  formatter = logging.Formatter('%(asctime)s - %(message)s')  ch.setFormatter(formatter)  p.addHandler(ch)    # 創建一個孩子logger  c = logging.getLogger('foo.bar')  c.debug('foo')

輸出結果:

[root@yaoliang day_12]# python test3.py  2016-10-17 17:56:01,375 - foo  2016-10-17 17:56:01,375 - DEBUG - foo

可見,孩子logger沒有任何handler,所以對消息不做處理。但是它把消息轉發給了它的父親以及root logger。最後輸出兩條日誌。

這也就出現了一個問題,同一條日誌會重複輸出

解決方案

1、每個logger實例都給一個獨立的名字,輸出之間互不影響,

2、logging.conf中定義不繼承

nginx + gunicorn + supervisor + flask

1、安裝gunicorn和supervisor

[root@yaoliang day_12]# pip install gunicorn supervisor

2、啟動gunicorn

[root@yaoliang homework_11]# ls  app  run.py  [root@yaoliang homework_11]# gunicorn -w4 -b0.0.0.0:9999 app:app -D  [root@yaoliang homework_11]# ps aux | grep gunicorn  root      43387  0.0  1.2 220196 12040 ?        S    17:42   0:00 gunicorn: master [app:app]  root      43392  0.1  1.9 324784 19844 ?        S    17:42   0:00 gunicorn: worker [app:app]  root      43393  0.1  1.9 324792 19848 ?        S    17:42   0:00 gunicorn: worker [app:app]  root      43394  0.1  1.9 324800 19856 ?        S    17:42   0:00 gunicorn: worker [app:app]  root      43397  0.1  1.9 324812 19864 ?        S    17:42   0:00 gunicorn: worker [app:app]  root      43474  0.0  0.0 112648   976 pts/0    R+   17:43   0:00 grep --color=auto gunicorn

此時可以通過9999埠進行訪問

  • -w:表示啟動多少個進程
  • -b:表示監聽的ip和埠
  • 第一個app:表示包含Flask(__name__)對象的模組或包
  • 第二個app:表示實例化Flask(__name__)對象
  • -D:表示以守護進程運行

3、通過supervisor,一個專門用來管理進程的工具來管理系統的進程。

  3.1、先生成配置文件

[root@yaoliang day_12]# echo_supervisord_conf > /etc/supervisor.conf

  3.2、修改配置文件,開啟web管理介面,並在/etc/supervisor.conf底部添加新配置

[inet_http_server]         ; inet (TCP) server disabled by default                       port=*:9001                ; (ip_address:port specifier, *:port for all iface)  username=user              ; (default is no username (open server))  password=123               ; (default is no password (open server))    [program:myapp]  command=/usr/bin/gunicorn -w4 -b0.0.0.0:9999 app:app                ; supervisor啟動命令  directory=/data/python/homework_11  startsecs=0                                                         ; 啟動時間  stopwaitsecs=0                                                      ; 終止等待時間  autostart=false                                                     ; 是否自動啟動  autorestart=false                                                   ; 是否自動重啟  stdout_logfile=/tmp/gunicorn.log                                    ; 日常輸出日誌  stderr_logfile=/tmp/gunicorn.err                                    ; 錯誤日誌

  3.3、supervisor的基本使用方法

supervisord -c /etc/supervisor.conf                        # 通過配置文件啟動supervisor  supervisorctl -c /etc/supervisor.conf status                    # 察看supervisor的狀態  supervisorctl -c /etc/supervisor.conf reload                    # 重新載入 配置文件  supervisorctl -c /etc/supervisor.conf start [all]|[appname]     # 啟動指定/所有 supervisor管理的程式進程  supervisorctl -c /etc/supervisor.conf stop [all]|[appname]      # 關閉指定/所有 supervisor管理的程式進程

  3.4、啟動supervisor

[root@yaoliang day_12]# supervisord -c /etc/supervisor.conf   [root@yaoliang day_12]# ps aux | grep supervisor  root      44393  0.0  1.1 224528 11308 ?        Ss   17:59   0:00 /usr/bin/python /usr/bin/supervisord -c /etc/supervisor.conf  root      44399  0.0  0.0 112648   980 pts/0    R+   17:59   0:00 grep --color=auto supervisor  [root@yaoliang day_12]# supervisorctl -c /etc/supervisor.conf status  myapp                            STOPPED   Not started  [root@yaoliang day_12]# supervisorctl -c /etc/supervisor.conf start myapp  myapp: started  [root@yaoliang day_12]# supervisorctl -c /etc/supervisor.conf status  myapp                            RUNNING   pid 44417, uptime 0:00:04

  3.5、通過nginx配置supervisor的web管理介面,並啟動

[root@yaoliang day_12]# vim /etc/nginx/nginx.conf      server {          listen       80;           server_name  localhost;            location / {               proxy_pass http://127.0.0.1:9001;          }      }   [root@yaoliang day_12]# systemctl start nginx

  3.6、訪問nginx