天天看點

Python學習筆記 - 常用子產品

前言

Python官方提供了數量衆多的子產品,稱為内置子產品。本文主要講解Python中一些在日常開發過程中常用的子產品,至于其他的不常用子產品可以自己在Python官網中查詢Python官方的API文檔。

Python 标準庫最新版

一 math子產品

Python官方提供math子產品進行數學運算,如指數、對數、平方根和三角函數等運算。math子產品中的函數隻是整數和浮點數,不包括複數,複數計算需要使用 cmath 子產品。

1.1 舍入函數

math子產品提供的舍入函數有:math.ceil(a) 和 math.floor(a),math.ceil(a) 用來傳回大于或等于 a 的最小整數,math.floor(a) 傳回小于或等于 a 的最大整數。另外,Python還提供了一個内置函數 round(a),該函數用來對 a 進行四舍五入計算。

在Python Shell 中運作示例代碼如下:

>>> math.ceil(1.4)
2
>>> math.floor(1.4)
1
>>> round(1.4)
1
>>> math.ceil(1.5)
2
>>> math.floor(1.5)
1
>>> round(1.5)
2
>>> math.ceil(1.6)
2
>>> math.floor(1.6)
1
>>> round(1.6)
2
           

1.2 幂和對數函數

math子產品提供的幂和對數函數如下所示。

  • 對數運算:math.log(a[, base]) 傳回以 base 為底的 a 的對數,省略底數 base,是 a 的自然數 e 的對數。
  • 平方根:math.sqrt(a) 傳回a的平方根。
  • 幂運算:math.pow(a, b) 傳回 a 的 b 次幂的值。

在 Python Shell 中運作示例代碼如下:

>>> math.log(8, 2)
3.0
>>> math.pow(2, 3)
8.0
>>> math.log(8)
2.0794415416798357
           

1.3 三角函數

math子產品中提供的三角函數有如下幾種:

  • math.sin(a):傳回弧度a的三角正弦。
  • math.cos(a):傳回弧度a的三角餘弦。
  • math.tan(a):傳回弧度a的三角正切。
  • math.asin(a):傳回弧度a的反正弦。
  • math.acos(a):傳回弧度a的反餘弦。
  • math.atan(a):傳回弧度a的反正切。

上述函數中a參數是弧度。有時需要将弧度轉換為角度,或将角度轉換為弧度,math子產品中提供了弧度和角度函數。

  • math.degree(a):将弧度a轉換為角度。
  • math.radians(a):将角度a轉換為弧度。

在 Python Shell 中運作示例代碼如下:

>>> math.degrees(0.5 * math.pi)     # 調用degrees()函數将弧度轉換為角度,其中math.pi是數學常量π
90.0
>>> math.radians(180 / math.pi)     # 調用radians()函數将角度轉換為弧度
1.0
>>> a = math.radians(45 / math.pi)  # 将45度角轉換為0.25弧度
>>> a
0.25
>>>
>>> math.sin(a)
0.24740395925452294
>>> math.asin(math.sin(a))
0.25
>>> math.asin(0.2474)
0.24999591371483254
>>> math.asin(0.24740395925452294)
0.25
>>>
>>> math.cos(a)
0.9689124217106447
>>> math.acos(0.9689124217106447)
0.2500000000000002
>>> math.acos(math.cos(a))
0.2500000000000002
>>>
>>> math.tan(a)
0.25534192122103627
>>> math.atan(math.tan(a))
0.25
>>> math.atan(0.25534192122103627)
0.25
           

二 random子產品

random子產品提供了一些生成随機數函數,相關函數見下述。

  • random.random():傳回在範圍大于或等于0.0,且小于1.0内的随機浮點數。
  • random.randrange(stop):傳回在範圍大于或等于0,且小于stop内,步長為1的随機整數。
  • random.randrange(start, stop[, step]):傳回在範圍大于或等于start,且小于stop内,步長為step的随機整數。
  • random.randint(a, b):傳回在範圍大于或等于a,且小于或等于b之間的随機整數。

示例代碼如下:

# coding=utf-8
# 代碼檔案:random_demo.py
# random随機數子產品使用示例

import random

# 0.0 <= x < 1.0 随機數
print('***** 0.0 <= x < 1.0 随機數 *****')
for i in range(0, 10):
    x = random.random() # 調用random()函數産生10個大于等于0.0小于1.0的随機浮點數
    print(x)

# 0 <= x < 10 随機數, 步長為1
print('***** 0 <= x < 10 随機數 *****')
for i in range(0, 10):
    x = random.randrange(10) # 調用randrange()函數産生10個大于等于0小于5的随機整數
    print(x, end = ' ')

print()
# 10<= x < 20 随機數, 步長為2
print('***** 10 <= x < 20 随機數 *****')
for i in range(0, 10):
    x = random.randrange(10, 20, 2) # 調用randrange()函數産生10個大于等于10小于20的随機整數,步長為2
    print(x, end = ' ')

print()
# 20 <= x <= 30 随機數
print('***** 20 <= x <= 30 随機數 *****')
for i in range(0, 10):
    x = random.randrange(20, 30) # 調用randrange()函數産生10個大于等于20或小于等于30的随機整數
    print(x, end = ' ')
print()
           

示例運作結果:

> python random_demo.py
***** 0.0 <= x < 1.0 随機數 *****
0.2600950020007613
0.5347034933571418
0.5197235867861837
0.2721707987274812
0.3731377067329801
0.23764883960218874
0.7946528038897872
0.22694072804616894
0.20968461237353753
0.5656559605330421
***** 0 <= x < 10 随機數 *****
4 7 6 3 9 1 2 2 2 8
***** 10 <= x < 20 随機數 *****
12 10 18 14 14 14 18 18 18 12
***** 20 <= x <= 30 随機數 *****
26 24 22 20 25 29 25 23 24 28
           

三 datetime子產品

Python官方提供的日期和時間子產品主要有time 和 datetime 子產品。time 側重于底層平台,子產品中大多數函數會調用本地平台上的C連結庫,是以有些函數運作結果,在不同平台上會有所不同。datetime子產品對time子產品進行了封裝,提供了更進階的API,是以本章節重點介紹datetime子產品的使用。

datetime子產品中提供了以下幾個類:

  • datetime類:包含時間和日期。
  • date類:隻包含日期。
  • time類:隻包含時間。
  • timedelta:計算時間跨度。
  • tzinfo類:時區資訊。

3.1 datetime、date 和 time 類

datetime子產品的核心類是 datetime、date 和 time 類。下面介紹如何建立這三種不同類的對象。

    • datetime類

一個datetime對象可以表示日期和時間等資訊,建立datetime對象可以使用如下構造方法:

datetime.datetime(year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None)
           

其中的year、month和day三個參數是不能省略的;tzinfo 是時區參數,預設值是None,表示不指定時區;除了tzinfo外,其他的參數全部為合理範圍内的整數。這些參數的取值範圍如下表3-1所示,注意如果超出這個範圍會抛出 ValueError 異常。

                                                                                               表3-1 參數取值範圍

Python學習筆記 - 常用子產品

在Python Shell 中運作示例代碼如下:

>>> import datetime                         # --1
>>> dt = datetime.datetime(2023, 2, 29)    # --2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: day is out of range for month
>>> dt = datetime.datetime(2023, 2, 28)
>>> dt
datetime.datetime(2023, 2, 28, 0, 0)
>>> dt = datetime.datetime(2023, 2, 28, 23, 60, 59, 10000) # --3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: minute must be in 0..59
>>> dt = datetime.datetime(2023, 2, 28, 23, 30, 59, 10000)
>>> dt
datetime.datetime(2023, 2, 28, 23, 30, 59, 10000)
>>>
           

在使用datetime子產品時需要導入子產品,見代碼第1處。代碼第2處試圖建立datetime對象,由于天數29超出了範圍,是以發生了ValueError異常。代碼第3處也發生了ValueError異常,因為minute參數超出了取值範圍。

除了通過構造方法建立并初始化datetime對象,還可以通過datetime類提供的一些類方法獲得datetime對象,這些類方法有以下幾種:

  • datetime.today():傳回目前本地日期和時間。
  • datetime.now(tz=None):傳回本地目前的日期和時間,如果參數tz為None或未指定,則等同于today()。
  • datetime.utcnow():傳回目前UTC日期和時間。
  • datetime.fromtimestamp(timestamp, tz=None):傳回與UNIX時間戳對應的本地日期和時間。
  • datetime.utcfromtimestamp(timestamp):傳回與UNIX時間戳對應的UTC日期和時間。
UTC( Universal Time Coordinated) 即協調世界時間,它以原子時為基礎,是時刻上盡量接近世界時的一種時間計量系統。UTC比GMT更加精準,它的出現滿足了現在社會對于精确計時的需要。GMT即格林威治标準時間,格林威治标準時間是19世紀中葉大英帝國的基準時間,同時也是世界基準時間。

在Python SHell 中運作示例代碼如下:

>>> import datetime
>>> datetime.datetime.today()                          # --1
datetime.datetime(2023, 2, 1, 0, 13, 26, 30545)
>>> datetime.datetime.now()                            # --2
datetime.datetime(2023, 2, 1, 0, 13, 55, 408356)
>>> datetime.datetime.utcnow()                         # --3
datetime.datetime(2023, 1, 31, 16, 14, 22, 770392)
>>> datetime.datetime.fromtimestamp(999999999.999)     # --4
datetime.datetime(2001, 9, 9, 9, 46, 39, 999000)
>>> datetime.datetime.utcfromtimestamp(999999999.999)  # --5
datetime.datetime(2001, 9, 9, 1, 46, 39, 999000)
>>>
           

從上述代碼運作結果可見,如果沒有指定時區,datetime.now() 和 datetime.today() 是相同的,見代碼第1處和第2處。代碼第3處中 datetime.utcnow() 與 datetime.today() 相比晚了8個小時,這是因為 datetime.today() 擷取的是本地時間,而筆者所在地是中原標準時間,即東八區,本地時間比UTC時間早8小時。代碼第4處和第5處通過時間戳建立datetime對象,從結果可見,同樣的時間戳 datetime.fromtimestamp() 比 datetime.utcfromtimestamp() 也是早8個小時。

《 注意1》在Python語言中時間戳機關是“秒”,是以它會有小數部分。而其他語言如Java機關是“毫秒”,當跨平台計算時間時需要注意這個差别。

《 注意2》時間戳的秒數是從1970年1月1日 00:00:00 以來到目前時刻的總秒數。

2. date類

一個date對象可以表示日期等資訊,建立date對象可以使用如下構造方法:

datetime.date(year, month, day)
           

其中的year、month和day三個參數是不能省略的,參數應為合理範圍内的整數,這些參數的取值範圍可以參考上表3-1,如果超出這個範圍會抛出異常ValueError。

在Python Shell 中運作示例代碼如下:

>>> import datetime
>>> d = datetime.date(2023, 2, 29)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: day is out of range for month
>>> d = datetime.date(2023, 2, 28)
>>> d
datetime.date(2023, 2, 28)
>>>

>>> import datetime
>>> datetime.date.today()
datetime.date(2023, 2, 1)
>>> datetime.date.fromtimestamp(999999999.999)
datetime.date(2001, 9, 9)
>>>
           

3. time類

一個time對象可以表示一天中時間資訊,建立time對象可以使用如下構造方法:

datetime.time(hour=0, minute=0, second=0, microsecond=0, tzinfo=None)
           

所有參數都是可選的,除tzinfo外,其他參數應為合理範圍内的整數,這些參數的取值範圍可以參考上表3-1,如果超出這個範圍會抛出異常。

在Python Shell 中運作示例代碼如下:

>>> import datetime
>>> datetime.time(24, 59, 59, 1999)    # --1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: hour must be in 0..23
>>> datetime.time(23, 59, 59, 1999)
datetime.time(23, 59, 59, 1999)
>>>
           

在使用time時需要導入datetime子產品。代碼第1處試圖建立time對象,由于一天時間不能超過24,是以發生ValueError異常。

3.2 日期時間計算

如果想知道10天之後是哪一天,或想知道2023年2月1日前5周是哪一天,就需要使用timedelta類了,timedelta對象用于計算datetime、date 和 time對象時間間隔。

timedelta類構造方法如下:

datetime.timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0)
           

所有參數都是可選的,參數可以為整數或浮點數,可以為正數或負數。在timedelta内部隻儲存days(天)、seconds(秒)和 microseconds(微秒)變量,是以其他參數milliseconds(毫秒)、minutes(分鐘)和weeks(周)都應換算為days、seconds和microseconds這三個參數。

在Python Shell 中運作示例代碼如下:

>>> import datetime
>>> datetime.date.today()
datetime.date(2023, 2, 4)
>>> d = datetime.date.today()              # 擷取目前本地日期
>>> delta = datetime.timedelta(10)         # 建立間隔時間為10天的timedelta對象
>>> d += delta                             # 目前日期+10天
>>> d
datetime.date(2023, 2, 14)
>>> d = datetime.date(2023, 1, 1)          # 建立2023年1月1日的日期對象
>>> delta = datetime.timedelta(weeks = 4)  # 建立時間間隔為4周的timedelta對象
>>> d += delta                             # 表示d後4周的日期
>>> d
datetime.date(2023, 1, 29)
           

本例中隻示範了日期的計算,使用timedelta對象可以精确到微秒,這裡不再贅述。

3.3 日期時間格式化和解析

無論是日期還是時間,當顯示在界面上時,都需要進行格式化輸出,使它能夠符合當地人檢視日期和時間的習慣。與日期時間格式化輸出相反的操作為日期時間的解析,當使用者使用輸入程式界面輸入日期時,計算機能夠讀入的是字元串,經過解析這些字元串獲得日期時間對象。

Python中日期時間格式化使用 strftime() 方法,datetime、date 和 time 這三個類中都有一個執行個體方法 strftime(format);而日期時間解析使用datetime.strptime(date_string, format) 類方法,date和time沒有strptime()方法。方法strftime()和strptime()中都有一個格式化參數format,用來控制日期時間的格式,如下表3-2所示,是常用的日期和時間格式控制符。

                                                             表3-2 常用的日期和時間格式控制符

Python學習筆記 - 常用子產品
Python學習筆記 - 常用子產品

《提示》上表3-2中的數字都是十進制數字。控制符會因不同平台有所差別,這是因為Python調用了本地平台C庫的strftime()函數而進行了日期和時間格式化。事實上這些控制符是1989版C語言控制符。表3-2隻是列出了常用的控制符,更多的控制符可參考如下連結:

strftime() 和 strptime() 的行為

在Python Shell 中運作示例代碼如下:

>>> import datetime
>>> d = datetime.datetime.today()    # 擷取目前本地日期時間對象
>>> d.strftime("%Y-%m-%d %H:%M:%S")  # 對目前日期時間對象d進行字元串格式化,d包含日期和時間資訊。如果隻關系其中部分資訊,在格式化時可以指定部分控制符
'2023-02-04 14:47:16'
>>> d.strftime("%Y-%m-%d")  # 對日期時間對象d進行格式化時隻指定部分控制符,這裡隻設定了年月日
'2023-02-04'
>>> 
>>> str_date = '2023-02-29 14:49:50'
>>> date = datetime.datetime.strptime(str_date, '%Y-%m-%d %H:%M:%S') # 試圖解析日期時間字元串str_date,但是2023年2月沒有29日,是以解析過程會抛出ValueError異常
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "D:\Program Files\Python\Lib\_strptime.py", line 568, in _strptime_datetime
    tt, fraction, gmtoff_fraction = _strptime(data_string, format)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\Program Files\Python\Lib\_strptime.py", line 534, in _strptime
    julian = datetime_date(year, month, day).toordinal() - \
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: day is out of range for month
>>> str_date = '2023-02-28 14:49:50'
>>> date = datetime.datetime.strptime(str_date, '%Y-%m-%d %H:%M:%S')
>>> date
datetime.datetime(2023, 2, 28, 14, 49, 50)
>>>
>>> date = datetime.datetime.strptime(str_date, '%Y-%m-%d') # 設定的控制符隻有年月日,而要解析的字元串是有時分秒的,是以會抛出ValueError異常
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "D:\Program Files\Python\Lib\_strptime.py", line 568, in _strptime_datetime
    tt, fraction, gmtoff_fraction = _strptime(data_string, format)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\Program Files\Python\Lib\_strptime.py", line 352, in _strptime
    raise ValueError("unconverted data remains: %s" %
ValueError: unconverted data remains:  14:49:50
>>>
           

3.4 時區

datetime和time對象隻是單純地表示本地的日期和時間,沒有時區資訊。如果想帶有時區資訊,可以使用timezone類,它是tzinfo的子類,提供了UTC偏移時區的實作。timezone類構造方法如下:

datetime.timezone(offset, name=None)
           

其中offset是UTC偏移量,+8是東八區,北京在此時區。-5是西五區,紐約在此時區,0是零時區,倫敦在此時區。name參數是時區名稱,如Asia/Beijing,可以省略不寫。

在Python Shell 中運作示例代碼如下:

>>> from datetime import datetime, timezone, timedelta                     # --1
>>> utc_dt = datetime(2023, 2, 4, 15, 6, 59, tzinfo = timezone.utc)       # --2 相當于 timezone(timedelta(0))
datetime.datetime(2023, 2, 4, 15, 6, 59, tzinfo=datetime.timezone.utc)
>>> utc_dt.strftime("%Y-%m-%d %H:%M:%S %Z")                                # --3
'2023-02-04 15:06:59 UTC'
>>> utc_dt.strftime("%Y-%m-%d %H:%M:%S %z")                                # --4
'2023-02-04 15:06:59 +0000'
>>> 
>>> bj_tz = timezone(offset = timedelta(hours = 8), name = 'Asia/Beijing') # --5
>>> bj_tz
datetime.timezone(datetime.timedelta(seconds=28800), 'Asia/Beijing')
>>> 
>>> bj_dt = utc_dt.astimezone(bj_tz)                                       # --6
>>> bj_dt
datetime.datetime(2023, 2, 4, 23, 6, 59, tzinfo=datetime.timezone(datetime.timedelta(seconds=28800), 'Asia/Beijing'))
>>> bj_dt.strftime('%Y-%m-%d %H:%M:%S %Z')
'2023-02-04 23:06:59 Asia/Beijing'
>>> bj_dt.strftime('%Y-%m-%d %H:%M:%S %z')
'2023-02-04 23:06:59 +0800'
>>>
>>> bj_tz = timezone(timedelta(hours = 8))                                 # --7
>>> bj_dt = utc_dt.astimezone(bj_tz)
>>> bj_dt.strftime('%Y-%m-%d %H:%M:%S %z')
'2023-02-04 23:06:59 +0800'
>>>
           

(1)上述代碼第1處采用from...import...語句導入datetime子產品,并明确指定導入datetime、timezone和timedelta類,這樣在使用時就不必在類名的前面再加上datetime子產品名了,使用起來比較簡潔。如果想導入所有的類可以使用:from datetime import * 語句。

(2)代碼第2處建立了datetime對象utc_dt,tzinfo=timezone.utc表示設定UTC時區,相當于timezone(timedelta(0))。

(3)代碼第3處和第4處分别格式化輸出utc_dt,它們都分别帶有時區控制符:%Z、%z。

(4)代碼第5處建立了timezone對象bj_tz,offset=timedelta(hours=8) 表示設定社群偏移量為東八區,即中原標準時間。name=Asia/Beijing是時區名,也可以省略不寫,見代碼第7處。完成時區建立還需要設定具體的datetime對象。

(5)代碼第6處utc_dt.astimezone(bj_tz) 是調整時區,傳回值bj_dt就變成中原標準時間了。

四 logging日志子產品

在程式開發過程中,有時需要輸出一些調試資訊;在程式釋出後,有時需要一些程式的運作資訊;在程式發生錯誤時需要輸出一些錯誤資訊。這些可以通過在程式中加入日志輸出實作。大部分開發人員會使用print()函數進行日志輸出,但是print()函數要想同時輸出日志資訊和時間、所在函數、所線上程等内容是比較困難的。讓print()函數輸出日志到檔案也是非常困難的。而且print()函數不能分級輸出日志,例如一些資訊是在調試階段輸出,在程式釋出後,不需要輸出這些資訊,這就是分級輸出,print()函數做不到這一點。

綜上所述,要想滿足複雜的日志輸出需求,不能使用print()函數,可以使用logging子產品,它是Python的内置子產品。

4.1 日志級别

logging子產品提供5種常用級别,如下表4-3所示。這5種級别從上到下由低到高。如果設定了DEBUG級别,debug()函數和其他級别的函數的日志資訊都會輸出;如果設定了ERROR級别,error()和critical()函數的日志資訊會輸出。

                                                                              表4-3  日志級别

Python學習筆記 - 常用子產品

示例代碼如下:

# coding=utf-8
# 代碼檔案: logging_level.py
# 使用logging子產品輸出不同日志級别的日志資訊

import logging
logging.basicConfig(level = logging.ERROR) # --1

logging.debug('這是 Debug 級别資訊')
logging.info('這是 INFO 級别資訊')
logging.warning('這是 WARNING 級别資訊')
logging.error('這是 ERROR 級别資訊')
logging.critical('這是 CRITICAL 級别資訊')
           

運作結果:

>python logging_level.py
ERROR:root:這是 ERROR 級别資訊
CRITICAL:root:這是 CRITICAL 級别資訊
           

上述代碼第1處對日志進行基本配置,level = logging.ERROR 中設定日志級别為ERROR。如果改變日志級别為DEBUG,所有日志函數資訊都能輸出,輸出的結果是:

>python logging_level.py
DEBUG:root:這是 Debug 級别資訊
INFO:root:這是 INFO 級别資訊
WARNING:root:這是 WARNING 級别資訊
ERROR:root:這是 ERROR 級别資訊
CRITICAL:root:這是 CRITICAL 級别資訊
           

在輸出的日志資訊中會有root關鍵字,這說明進行日志輸出的對象是root日志器(logger)。也可以使用getLogger()函數建立自己的日志器對象,代碼如下:

logger = logging.getLogger(__name__)
           

getLogger()函數的參數是一個字元串,本例中 __name__ 是目前子產品名。使用自定義日志器的示例代碼如下:

# coding=utf-8
# 代碼檔案: logging_level2.py
# 使用logging子產品輸出不同日志級别的日志資訊

import logging
logging.basicConfig(level = logging.DEBUG)

logger = logging.getLogger(__name__)  # --1:建立日志器對象

logger.debug('這是 Debug 級别資訊')   # --2
logger.info('這是 INFO 級别資訊')
logger.warning('這是 WARNING 級别資訊')
logger.error('這是 ERROR 級别資訊')
logger.critical('這是 CRITICAL 級别資訊')
           

運作結果如下:

>python logging_level2.py
DEBUG:__main__:這是 Debug 級别資訊
INFO:__main__:這是 INFO 級别資訊
WARNING:__main__:這是 WARNING 級别資訊
ERROR:__main__:這是 ERROR 級别資訊
CRITICAL:__main__:這是 CRITICAL 級别資訊
           

上述代碼第1處是自定義一個日志器logger對象,有了日志器對象就可以通過日志器對象調用日志函數了,見代碼第2處。

4.2 日志資訊格式化

可以根據自己的需要設定日志資訊的格式化布局。常用的格式化參數如下表4-4所示。

                                                                       表4-4  日志資訊格式化

Python學習筆記 - 常用子產品

示例代碼如下:

# coding=utf-8
# 代碼檔案: logging_format.py
# 根據需要設定日志資訊的格式化布局

import logging
logging.basicConfig(level = logging.INFO, format='%(asctime)s - %(threadName)s - %(name)s - %(funcName)s - %(levelname)s'
                                                 ' - %(message)s')  # --1

logger = logging.getLogger(__name__)  # 建立日志器對象

logger.debug('這是 Debug 級别資訊')
logger.info('這是 INFO 級别資訊')
logger.warning('這是 WARNING 級别資訊')
logger.error('這是 ERROR 級别資訊')
logger.critical('這是 CRITICAL 級别資訊')

def funlog():
    logger.info('進入 funlog 函數')

logger.info('調用 funlog 函數')
funlog()
           

運作結果:

>python logging_format.py
2023-02-07 01:45:17,756 - MainThread - __main__ - <module> - INFO - 這是 INFO 級别資訊
2023-02-07 01:45:17,756 - MainThread - __main__ - <module> - WARNING - 這是 WARNING 級别資訊
2023-02-07 01:45:17,756 - MainThread - __main__ - <module> - ERROR - 這是 ERROR 級别資訊
2023-02-07 01:45:17,756 - MainThread - __main__ - <module> - CRITICAL - 這是 CRITICAL 級别資訊
2023-02-07 01:45:17,756 - MainThread - __main__ - <module> - INFO - 調用 funlog 函數
2023-02-07 01:45:17,756 - MainThread - __main__ - funlog - INFO - 進入 funlog 函數
           

上述代碼第1處是通過basicConfig()函數的format參數設定日志格式化資訊。從輸出結果可見,%(name)s格式化輸出日志器名稱為:__main__;%(funcName)s 格式化輸出函數名為:MainThread。

4.3 日志重定向

日志資訊預設是輸出到控制台的,也可以将日志資訊輸出到日志檔案中,甚至可以輸出到網絡中的其他計算機。

輸出到日志檔案中的示例代碼如下:

# coding=utf-8
# 代碼檔案: logging_redirect.py
# 将日志重定向到日志檔案中

import logging
logging.basicConfig(level = logging.INFO, format='%(asctime)s - %(threadName)s - %(name)s - %(funcName)s - %(levelname)s'
                                                 ' - %(message)s', filename = 'test.log')  # --1

logger = logging.getLogger(__name__)  # 建立日志器對象

logger.debug('這是 Debug 級别資訊')
logger.info('這是 INFO 級别資訊')
logger.warning('這是 WARNING 級别資訊')
logger.error('這是 ERROR 級别資訊')
logger.critical('這是 CRITICAL 級别資訊')

def funlog():
    logger.info('進入 funlog 函數')

logger.info('調用 funlog 函數')
funlog()
           

上述代碼第1處basicConfig()函數的參數 filename = 'test.log',是設定日志檔案名,也包括路徑,但是需要注意的是,路徑中的目錄名是存在的。程式運作時日志不會在控制台輸出,而是寫入目前目錄中的 test.log 檔案,預設情況下日志資訊是不斷追加到檔案中的。

test.log日志檔案内容如下:

2023-02-04 18:10:18,476 - MainThread - __main__ - <module> - INFO - 這是 INFO 級别資訊
2023-02-04 18:10:18,476 - MainThread - __main__ - <module> - WARNING - 這是 WARNING 級别資訊
2023-02-04 18:10:18,476 - MainThread - __main__ - <module> - ERROR - 這是 ERROR 級别資訊
2023-02-04 18:10:18,476 - MainThread - __main__ - <module> - CRITICAL - 這是 CRITICAL 級别資訊
2023-02-04 18:10:18,476 - MainThread - __main__ - <module> - INFO - 調用 funlog 函數
2023-02-04 18:10:18,476 - MainThread - __main__ - funlog - INFO - 進入 funlog 函數
           

4.4 使用配置檔案

上面的日志示例中,日志資訊的配置都是在basicConfig()函數中進行的,這樣就不是很友善,每次修改日志配置時都要修改程式代碼。特别是在程式釋出後,系統維護人員要修改代碼,可能會引起嚴重的責任問題。此時,可以使用配置檔案,配置資訊可以從配置檔案中讀取。

從配置檔案中讀取的示例代碼如下:

# coding=utf-8
# 代碼檔案: logging_config.py
# 使用日志配置檔案來配置日志輸出的格式

import logging
import logging.config

logging.config.fileConfig("logger.conf") # --1

logger = logging.getLogger('logger1')    # --2

logger.debug('這是 Debug 級别資訊')
logger.info('這是 INFO 級别資訊')
logger.warning('這是 WARNING 級别資訊')
logger.error('這是 ERROR 級别資訊')
logger.critical('這是 CRITICAL 級别資訊')

def funlog():
    logger.info('進入 funlog 函數')

logger.info('調用 funlog 函數')
funlog()
           

代碼第1處是指定配置資訊是從配置檔案logger.conf中讀取的,代碼第2處是從配置檔案中讀取logger1配置資訊建立日志器。

配置檔案logger.conf内容如下:

[loggers]               # 配置日志器 --1
keys=root,simpleExample # 日志器包含了root和simpleExample

[logger_root] # 配置根日志器
level=DEBUG
handlers=consoleHandler # 日志器對應的處理器

[logger_simpleExample] # 配置simpleExample日志器
level=DEBUG
handlers=fileHandler   # 日志器對應的處理器
qualname=logger1       # 設定日志器名稱

[handlers]             # 配置處理器 --2
keys=consoleHandler,fileHandler # 包含了兩個處理器

[handler_consoleHandler] # 配置 consoleHandler 處理器
class=StreamHandler      # --3
level=DEBUG
formatter=simpleFormatter # --4
args=(sys.stdout,)        # 輸出流是終端控制台 --5

[handler_fileHandler]  # 配置 fileHandler 處理器
class=FileHandler      # --6
level=DEBUG
formatter=simpleFormatter
args=('test2.log', 'a')  # 指定輸出日志檔案 --7

[formatters]          # 配置格式化器 --8
keys=simpleFormatter  # 日志器包含 simpleFormatter --9

[formatter_simpleFormatter] # 配置 simpleFormatter 格式化器 --10
format=%(asctime)s-%(levelname)s-%(message)s
           

在配置檔案中需要配置主要項目是日志器、處理器和格式化器,見代碼第1處、第2處和第8處。其中處理器就是指定日志資訊輸出到哪裡,包括控制台、檔案和網絡等;格式化器就是将日志資訊進行格式化。它們之間的依賴關系是,日志器依賴于處理器,處理器依賴于格式化器。

這三個配置又可以分為多個子配置資訊,例如日志器可以分為root和simpleExample兩個,處理器有consoleHandler 和 fileHandler 兩個,格式化器隻有simpleFormatter一個。

下面重點介紹一下處理器。代碼第3處設定處理器類是StreamHandler,即輸入輸出流處理器,包括控制台、網絡等。代碼第4處設定該處理器采用的格式化器。代碼第5處設定輸出流是控制台。代碼第6處是設定處理器類是FileHandler,即檔案處理器。代碼第7處是指定輸出檔案為test2.log,并以追加的方式打開檔案。

logging_config.py 代碼中指定的日志器名是logger1,也就是配置檔案中simpleExample日志器,它依賴的處理器是fileHandler,fileHandler處理器依賴的配置格式化器是simpleFormatter。

參考

《Python從小白到大牛(第1版-2018).pdf》第13章 - 常用子產品