經常使用模塊之hashlib,subprocess,logging,re,collections

hashlib

什麼是hashlib

什麼叫hash:hash是一種算法(3.x裏代替了md5模塊和sha模塊,主要提供 SHA1, SHA224, SHA256, SHA384, SHA512 ,MD5 算法),該算法接受傳入的內容,通過運算獲得一串hash值

hash值的特色是:
只要傳入的內容同樣,獲得的hash值必然同樣=====>要用明文傳輸密碼文件完整性校驗
不能由hash值返解成內容=======》把密碼作成hash值,不該該在網絡傳輸明文密碼
只要使用的hash算法不變,不管校驗的內容有多大,獲得的hash值長度是固定的

什麼是摘要算法呢?摘要算法又稱哈希算法、散列算法。它經過一個函數,把任意長度的數據轉換爲一個長度固定的數據串(一般用16進制的字符串表示)。html

摘要算法就是經過摘要函數f()對任意長度的數據data計算出固定長度的摘要digest,目的是爲了發現原始數據是否被人篡改過。python

摘要算法之因此能指出數據是否被篡改過,就是由於摘要函數是一個單向函數,計算f(data)很容易,但經過digest反推data卻很是困難。並且,對原始數據作一個bit的修改,都會致使計算出的摘要徹底不一樣。git

咱們以常見的摘要算法MD5爲例,計算出一個字符串的MD5值正則表達式

import hashlib

data = 'how to use md5 in python hashlib?'
md5 = hashlib.md5(data.encode('utf-8'))
print(md5.hexdigest())
計算結果以下:
d26a53750bc40b38b65a520292f69306

若是數據量很大,能夠分塊屢次調用update(),最後計算的結果是同樣的:算法

import hashlib
md5 = hashlib.md5()
md5.update(b'how to use md5 in ')
md5.update(b'python hashlib?')
print(md5.hexdigest())

d03b3899d2d6ac723a4e70db7ca2b83f
View Code

以上是對於英文進行md5加密的,若是要對中文進行加密,發現按照上面來寫會報錯,緣由在於字符轉碼問題shell

#中文加密
m1 = hashlib.sha512()
str_cn ='你好,世界'
#中文形式時要指定編碼方式
m1.update(str_cn.encode("utf-8"))
print(m1.hexdigest())

我要用md5加密圖片名字,爬取圖片的時候防止圖片重複出現。把它放到下載圖片循環裏,例如:數據庫

for ii in i.xpath('div/div/img/@data-original'):
    img_url = ii[2:]
    wei = img_url[-4:]
    md5 = hashlib.md5(wei.encode("gb2312"))
    listss = md5.hexdigest()
    if listss in ['.jpg','.gif','.png']:
        make_files(img_name + '\\' + str(random.randint(1, 99999999999999)) + listss, img_url)
    else:
        print(img_url)
View Code

 以上加密算法雖然依然很是厲害,但時候存在缺陷,即:經過撞庫能夠反解。因此,有必要對加密算法中添加自定義key再來作加密。express

import hashlib
passwds=[
    'alex3714',
    'alex1313',
    'alex94139413',
    'alex123456',
    '123456alex',
    'a123lex',
    ]
def make_passwd_dic(passwds):
    dic={}
    for passwd in passwds:
        m=hashlib.md5()
        m.update(passwd.encode('utf-8'))
        dic[passwd]=m.hexdigest()
    return dic

def break_code(cryptograph,passwd_dic):
    for k,v in passwd_dic.items():
        if v == cryptograph:
            print('密碼是===>\033[46m%s\033[0m' %k)

cryptograph='aee949757a2e698417463d47acac93df'
break_code(cryptograph,make_passwd_dic(passwds))
模擬撞庫

python 還有一個 hmac 模塊,它內部對咱們建立 key 和 內容 進行進一步的處理而後再加密django

import hmac
h = hmac.new('alvin'.encode('utf8'))
h.update('hello'.encode('utf8'))
print (h.hexdigest())#320df9832eab4c038b6c1d7ed73a5940


要想保證hmac最終結果一致,必須保證:
1:hmac.new括號內指定的初始key同樣
2:不管update多少次,校驗的內容累加到一塊兒是同樣的內容

import hmac

h1=hmac.new(b'egon')
h1.update(b'hello')
h1.update(b'world')
print(h1.hexdigest())

h2=hmac.new(b'egon')
h2.update(b'helloworld')
print(h2.hexdigest())

h3=hmac.new(b'egonhelloworld')
print(h3.hexdigest())

'''
f1bf38d054691688f89dcd34ac3c27f2
f1bf38d054691688f89dcd34ac3c27f2
bcca84edd9eeb86f30539922b28f3981
'''
View Code
任何容許用戶登陸的網站都會存儲用戶登陸的用戶名和口令。如何存儲用戶名和口令呢?方法是存到數據庫表中:

name    | password
michael | 123456
bob     | abc999
alice   | alice2008
若是以明文保存用戶口令,若是數據庫泄露,全部用戶的口令就落入黑客的手裏。此外,網站運維人員是能夠訪問數據庫的,也就是能獲取到全部用戶的口令。正確的保存口令的方式是不存儲用戶的明文口令,而是存儲用戶口令的摘要,好比MD5:

username | password
michael  | e10adc3949ba59abbe56e057f20f883e
bob      | 878ef96e86145580c38c87f0410ad153
alice    | 99b1c2188db85afee403b1536010c2c9
考慮這麼個狀況,不少用戶喜歡用123456,888888,password這些簡單的口令,因而,黑客能夠事先計算出這些經常使用口令的MD5值,獲得一個反推表:

'e10adc3949ba59abbe56e057f20f883e': '123456'
'21218cca77804d2ba1922c33e0151105': '888888'
'5f4dcc3b5aa765d61d8327deb882cf99': 'password'
這樣,無需破解,只須要對比數據庫的MD5,黑客就得到了使用經常使用口令的用戶帳號。
對於用戶來說,固然不要使用過於簡單的口令。可是,咱們可否在程序設計上對簡單口令增強保護呢?

因爲經常使用口令的MD5值很容易被計算出來,因此,要確保存儲的用戶口令不是那些已經被計算出來的經常使用口令的MD5,這一方法經過對原始口令加一個複雜字符串來實現,俗稱「加鹽」:

hashlib.md5("salt".encode("utf8"))
通過Salt處理的MD5口令,只要Salt不被黑客知道,即便用戶輸入簡單口令,也很難經過MD5反推明文口令。

可是若是有兩個用戶都使用了相同的簡單口令好比123456,在數據庫中,將存儲兩條相同的MD5值,這說明這兩個用戶的口令是同樣的。有沒有辦法讓使用相同口令的用戶存儲不一樣的MD5呢?

若是假定用戶沒法修改登陸名,就能夠經過把登陸名做爲Salt的一部分來計算MD5,從而實現相同口令的用戶也存儲不一樣的MD5。

摘要算法在不少地方都有普遍的應用。要注意摘要算法不是加密算法,不能用於加密(由於沒法經過摘要反推明文),只能用於防篡改,可是它的單向計算特性決定了能夠在不存儲明文口令的狀況下驗證用戶口令。
摘要算法的應用與一些面臨的問題解決方案

subprocess

咱們常常須要經過Python去執行一條系統命令或腳本,系統的shell命令是獨立於你的python進程以外的,每執行一條命令,就是發起一個新進程,經過python調用系統命令或腳本的模塊在python2有os.system,json

>> os.system('uname -a')
Darwin Alexs-MacBook-Pro.local 15.6.0 Darwin Kernel Version 15.6.0: Sun Jun  4 21:43:07 PDT 2017; root:xnu-3248.70.3~1/RELEASE_X86_64 x86_64
0
View Code

這條命令的實現原理是什麼呢

除了os.system能夠調用系統命令,,commands,popen2等也能夠,比較亂,因而官方推出了subprocess,目地是提供統一的模塊來實現對系統命令或腳本的調用

The subprocess module allows you to spawn new processes, connect to their input/output/error pipes, and obtain their return codes. This module intends to replace several older modules and functions:

  • os.system
  • os.spawn*

The recommended approach to invoking subprocesses is to use the run() function for all use cases it can handle. For more advanced use cases, the underlying Popen interface can be used directly.

The run() function was added in Python 3.5; if you need to retain compatibility with older versions, see the Older high-level API section.

三種執行命令的方法

  • subprocess.run(*popenargs, input=None, timeout=None, check=False, **kwargs) #官方推薦

  • subprocess.call(*popenargs, timeout=None, **kwargs) #跟上面實現的內容差很少,另外一種寫法

  • subprocess.Popen() #上面各類方法的底層封裝

run()方法

源碼解釋

使用參數運行命令並返回一個CompletedProcess實例。返回的實例將具備屬性args、returncode、stdout和stderr。默認狀況下,stdout和stderr沒有被捕獲,這些屬性將是None。經過stdout=管道和/或stderr=管道來捕獲它們。

若是檢查爲真,退出代碼爲非零,則會產生一個稱爲processerror。名爲processerror的對象將在returncode屬性中具備返回代碼,若是捕獲了這些流,則輸出和stderr屬性

若是給定了超時,而且進程花費的時間太長,將會拋出一個超時過時的異常。

其餘參數與Popen構造函數相同。

標準寫法

subprocess.run(['df','h'],stderr=subprocess.PIPE,stdout=subprocess.PIPE,check=True)

涉及到管道|的命令須要這樣寫

subprocess.run('df -h|grep disk1',shell=True) #shell=True的意思是這條命令直接交給系統去執行,不須要python負責解析

call()方法

#執行命令,返回命令執行狀態 , 0 or 非0
>>> retcode = subprocess.call(["ls", "-l"])
 
#執行命令,若是命令結果爲0,就正常返回,不然拋異常
>>> subprocess.check_call(["ls", "-l"])
0
 
#接收字符串格式命令,返回元組形式,第1個元素是執行狀態,第2個是命令結果 
>>> subprocess.getstatusoutput('ls /bin/ls')
(0, '/bin/ls')
 
#接收字符串格式命令,並返回結果
>>> subprocess.getoutput('ls /bin/ls')
'/bin/ls'
 
#執行命令,並返回結果,注意是返回結果,不是打印,下例結果返回給res
>>> res=subprocess.check_output(['ls','-l'])
>>> res
b'total 0\ndrwxr-xr-x 12 alex staff 408 Nov 2 11:05 OldBoyCRM\n'
View Code

Popen()方法

經常使用參數:

args:shell命令,能夠是字符串或者序列類型(如:list,元組)
 
 
stdin, stdout, stderr:分別表示程序的標準輸入、輸出、錯誤句柄
 
preexec_fn:只在Unix平臺下有效,用於指定一個可執行對象(callable object),
它將在子進程運行以前被調用
 
shell:同上
 
cwd:用於設置子進程的當前目錄
 
env:用於指定子進程的環境變量。若是env = None,子進程的環境變量將從父進程中繼承。
View Code 

下面這2條語句執行會有什麼區別?

a=subprocess.run('sleep 10',shell=True,stdout=subprocess.PIPE)
a=subprocess.Popen('sleep 10',shell=True,stdout=subprocess.PIPE)

區別是Popen會在發起命令後馬上返回,而不等命令執行結果。這樣的好處是什麼呢?

若是你調用的命令或腳本 須要執行10分鐘,你的主程序不需卡在這裏等10分鐘,能夠繼續往下走,幹別的事情,每過一會,經過一個什麼方法來檢測一下命令是否執行完成就行了。

執行shell腳本

執行shell腳本這個有多種方法   最後仍是選擇了subprocess這個python標準庫

subprocess這個模塊能夠很是方便的啓動一個子進程,而且控制其輸入和輸出

Class Popen(args,bufsize = 0,executable=None,
            stdin =None,stdout =None,stderr =None,
            preexec_fn = None,close_fds = False,shell = False,
            cwd = None,env = None,universal_newlines = False,
            startupinfo = None,creationflags = 0):
參數是:
args 應該是一個字符串,或一系列程序參數。要執行的程序一般是args序列或字符串中的第一項,但可使用可執行參數進行顯式設置。
在UNIX上,與shell=False(默認):在這種狀況下,POPEN 類使用os.execvp()來執行子程序。 args一般應該是一個序列。一個字符串將被視爲一個字符串做爲惟一項目(要執行的程序)的序列。

在UNIX上,使用shell = True:若是args是一個字符串,則它指定要經過shell執行的命令字符串。若是args是一個序列,則第一個項目指定命令字符串,而且任何其餘項目將被視爲附加的shell參數。

能夠先建立一個簡單的shell腳本  a.sh

$1 $2 分別表明傳進腳本的 第一個和第二個參數

若是不寫shell=True,默認爲shell=False,須要在args的第一個參數指定執行器路徑

bufsize  若是給出,bufsize與內建的open()函數的相應參數具備相同的含義:0表示無緩衝,1表示行緩衝,任何其餘正值意味着使用(大約)該大小的緩衝區。負bufsize意味着使用系統默認值,一般意味着徹底緩衝。bufsize的默認值是0(無緩衝)。

stdin,stdout和stderr分別指定執行的程序的標準輸入,標準輸出和標準錯誤文件句柄。有效值是PIPE,現有文件描述符(正整數),現有文件對象和 None。 PIPE表示應該建立一個新的管道給孩子。隨着無,則不會發生重定向; 孩子的文件句柄將從父類繼承。另外,stderr 能夠是STDOUT,它表示應用程序的stderr數據應該被捕獲到與stdout相同的文件句柄中。
在Popen對象中,能夠設值subprocess.stdout=PIPE 即經過管道 p.stdout.read()取出 該進程的標準輸出

preexec_fn 若是將preexec_fn設置爲可調用對象,則該對象將在子進程執行前被調用。

若是close_fds爲true,則在執行子進程以前,將關閉除0,1和2以外的全部文件描述符。

若是shell爲true,則指定的命令將經過shell執行。

若是cwd不是None,那麼在執行子代以前,當前目錄將更改成cwd。

若是env不是None,它將爲新進程定義環境變量。

若是設置universal_newlines爲true,則文件對象stdout和stderr將做爲文本文件打開,但可能會有\ n,Unix行尾約定\ r,Macintosh約定或\ r \ n中的任何行終止, Windows約定。全部這些外部表示被Python程序視爲\ n。注意:此功能僅在Python是使用通用換行支持(默認)構建時纔可用。此外,文件對象stdout,stdin和stderr的newlines屬性不會被communications()方法更新。

若是設置了STARTUPINFO和creationflags,將被傳遞到下層的CreateProcess()函數。他們能夠指定諸如主窗口的外觀和新過程的優先級等內容。(僅限Windows)

 

Popen調用後會返回一個對象,能夠經過這個對象拿到命令執行結果或狀態等,該對象有如下方法

poll()
    
    Check if child process has terminated. Returns returncode
    檢查子進程是否已終止。返回returncode

wait()
    Wait for child process to terminate. Returns returncode attribute.
    等待子進程終止。返回returncode屬性
 
terminate()
    終止所啓動的進程   Terminate the process with SIGTERM
 
kill()
    殺死所啓動的進程    Kill the process with SIGKILL
 
communicate()
    與啓動的進程交互,發送數據到stdin,並從stdout接收輸出,而後等待任務結束
 
send_signal(signal.xxx)
  發送系統信號
 
pid 
  拿到所啓動進程的進程號
View Code
>>> a = subprocess.Popen('python3 guess_age.py',stdout=subprocess.PIPE,stderr=subprocess.PIPE,stdin=subprocess.PIPE,shell=True)
 
>>> a.communicate(b'22')
 
(b'your guess:try bigger\n', b'')
View Code

詳細的參考官方文檔:subprocess

logging

CRITICAL = 50 #FATAL = CRITICAL
ERROR = 40
WARNING = 30 #WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0 #不設置
import logging

logging.debug('調試debug')
logging.info('消息info')
logging.warning('警告warn')
logging.error('錯誤error')
logging.critical('嚴重critical')

'''
WARNING:root:警告warn
ERROR:root:錯誤error
CRITICAL:root:嚴重critical
'''
默認級別爲warning時纔打印到終端

爲logging模塊指定全局配置,包括打印格式,針對全部logger有效,控制打印到文件中

可在logging.basicConfig()函數中可經過具體參數來更改logging模塊默認行爲,可用參數有
filename:用指定的文件名建立FiledHandler(後邊會具體講解handler的概念),這樣日誌會被存儲在指定的文件中。
filemode:文件打開方式,在指定了filename時使用這個參數,默認值爲「a」還可指定爲「w」。
format:指定handler使用的日誌顯示格式。
datefmt:指定日期時間格式。
level:設置rootlogger(後邊會講解具體概念)的日誌級別
stream:用指定的stream建立StreamHandler。能夠指定輸出到sys.stderr,sys.stdout或者文件,默認爲sys.stderr。若同時列出了filename和stream兩個參數,則stream參數會被忽略。


format參數中可能用到的格式化串:
%(name)s Logger的名字
%(levelno)s 數字形式的日誌級別
%(levelname)s 文本形式的日誌級別
%(pathname)s 調用日誌輸出函數的模塊的完整路徑名,可能沒有
%(filename)s 調用日誌輸出函數的模塊的文件名
%(module)s 調用日誌輸出函數的模塊名
%(funcName)s 調用日誌輸出函數的函數名
%(lineno)d 調用日誌輸出函數的語句所在的代碼行
%(created)f 當前時間,用UNIX標準的表示時間的浮 點數表示
%(relativeCreated)d 輸出日誌信息時的,自Logger建立以 來的毫秒數
%(asctime)s 字符串形式的當前時間。默認格式是 「2003-07-08 16:49:45,896」。逗號後面的是毫秒
%(thread)d 線程ID。可能沒有
%(threadName)s 線程名。可能沒有
%(process)d 進程ID。可能沒有
%(message)s用戶輸出的消息


import logging
logging.basicConfig(filename='access.log',
                    format='%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s',
                    datefmt='%Y-%m-%d %H:%M:%S %p',
                    level=10)

logging.debug('調試debug')
logging.info('消息info')
logging.warning('警告warn')
logging.error('錯誤error')
logging.critical('嚴重critical')


access.log內容:
2017-07-28 20:32:17 PM - root - DEBUG -test:  調試debug
2017-07-28 20:32:17 PM - root - INFO -test:  消息info
2017-07-28 20:32:17 PM - root - WARNING -test:  警告warn
2017-07-28 20:32:17 PM - root - ERROR -test:  錯誤error
2017-07-28 20:32:17 PM - root - CRITICAL -test:  嚴重critical

 logging模塊的Formatter,Handler,Logger,Filter對象

logger:產生日誌的對象
Filter:過濾日誌的對象
Handler:接收日誌而後控制打印到不一樣的地方,FileHandler用來打印到文件中,StreamHandler用來打印到終端
Formatter對象:能夠定製不一樣的日誌格式對象,而後綁定給不一樣的Handler對象使用,以此來控制不一樣的Handler的日誌格式

 

'''
critical=50
error =40
warning =30
info = 20
debug =10
'''
import logging


一、logger對象:負責產生日誌,而後交給Filter過濾,而後交給不一樣的Handler輸出
logger=logging.getLogger(__file__)

二、Filter對象:不經常使用,略

三、Handler對象:接收logger傳來的日誌,而後控制輸出
h1=logging.FileHandler('t1.log') #打印到文件
h2=logging.FileHandler('t2.log') #打印到文件
h3=logging.StreamHandler() #打印到終端

其餘一些經常使用的方法:
Handler.__init__(level=NOTSET) 根據日誌級別初始化處理器,設置過濾器列表和建立一個鎖來訪問I/O。當派生類繼承本類時,須要在構造函數裏調用本函數
Handler.createLock() 建立一個鎖以便在多線程下安全使用
Handler.acquire() 獲取線程鎖
Handler.release() 釋放線程鎖
Handler.flush()
確保全部日誌都已經輸出
Handler.close() 
回收全部使用的資源

四、Formatter對象:日誌格式
formmater1=logging.Formatter('%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s',
                    datefmt='%Y-%m-%d %H:%M:%S %p',)
formmater2=logging.Formatter('%(asctime)s :  %(message)s',
                    datefmt='%Y-%m-%d %H:%M:%S %p',)
formmater3=logging.Formatter('%(name)s %(message)s',)


五、爲Handler對象綁定格式
h1.setFormatter(formmater1)
h2.setFormatter(formmater2)
h3.setFormatter(formmater3)

六、將Handler添加給logger並設置日誌級別
logger.addHandler(h1)
logger.addHandler(h2)
logger.addHandler(h3)
logger.setLevel(10)

七、測試
logger.debug('debug')
logger.info('info')
logger.warning('warning')
logger.error('error')
logger.critical('critical')

 

Logger與Handler的級別

logger是第一級過濾,而後才能到handler,咱們能夠給logger和handler同時設置level,可是須要注意的是

Logger也是第一個基於級別過濾消息的人——若是您將Logger設置爲INFO,全部的處理程序都設置爲DEBUG,您仍然不會在處理程序上接收調試消息——它們將被Logger本身拒絕。若是您將logger設置爲DEBUG,可是全部的處理程序都設置爲INFO,那麼您也不會收到任何調試消息——由於當記錄器說「ok,處理這個」時,處理程序會拒絕它(DEBUG < INFO)。



#驗證
import logging


form=logging.Formatter('%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s',
                    datefmt='%Y-%m-%d %H:%M:%S %p',)

ch=logging.StreamHandler()

ch.setFormatter(form)
# ch.setLevel(10)
ch.setLevel(20)

l1=logging.getLogger('root')
# l1.setLevel(20)
l1.setLevel(10)
l1.addHandler(ch)

l1.debug('l1 debug')
View Code

logger的繼承

import logging

formatter=logging.Formatter('%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s',
                    datefmt='%Y-%m-%d %H:%M:%S %p',)

ch=logging.StreamHandler()
ch.setFormatter(formatter)


logger1=logging.getLogger('root')
logger2=logging.getLogger('root.child1')
logger3=logging.getLogger('root.child1.child2')


logger1.addHandler(ch)
logger2.addHandler(ch)
logger3.addHandler(ch)
logger1.setLevel(10)
logger2.setLevel(10)
logger3.setLevel(10)

logger1.debug('log1 debug')
logger2.debug('log2 debug')
logger3.debug('log3 debug')
'''
2017-07-28 22:22:05 PM - root - DEBUG -test:  log1 debug
2017-07-28 22:22:05 PM - root.child1 - DEBUG -test:  log2 debug
2017-07-28 22:22:05 PM - root.child1 - DEBUG -test:  log2 debug
2017-07-28 22:22:05 PM - root.child1.child2 - DEBUG -test:  log3 debug
2017-07-28 22:22:05 PM - root.child1.child2 - DEBUG -test:  log3 debug
2017-07-28 22:22:05 PM - root.child1.child2 - DEBUG -test:  log3 debug
View Code

logger的應用

"""
logging配置
"""

import os
import logging.config

# 定義三種日誌輸出格式 開始

standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \
                  '[%(levelname)s][%(message)s]' #其中name爲getlogger指定的名字

simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'

id_simple_format = '[%(levelname)s][%(asctime)s] %(message)s'

# 定義日誌輸出格式 結束

logfile_dir = os.path.dirname(os.path.abspath(__file__))  # log文件的目錄

logfile_name = 'all2.log'  # log文件名

# 若是不存在定義的日誌目錄就建立一個
if not os.path.isdir(logfile_dir):
    os.mkdir(logfile_dir)

# log文件的全路徑
logfile_path = os.path.join(logfile_dir, logfile_name)

# log配置字典
LOGGING_DIC = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': standard_format
        },
        'simple': {
            'format': simple_format
        },
    },
    'filters': {},
    'handlers': {
        #打印到終端的日誌
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',  # 打印到屏幕
            'formatter': 'simple'
        },
        #打印到文件的日誌,收集info及以上的日誌
        'default': {
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件
            'formatter': 'standard',
            'filename': logfile_path,  # 日誌文件
            'maxBytes': 1024*1024*5,  # 日誌大小 5M
            'backupCount': 5,
            'encoding': 'utf-8',  # 日誌文件的編碼,不再用擔憂中文log亂碼了
        },
    },
    'loggers': {
        #logging.getLogger(__name__)拿到的logger配置
        '': {
            'handlers': ['default', 'console'],  # 這裏把上面定義的兩個handler都加上,即log數據既寫入文件又打印到屏幕
            'level': 'DEBUG',
            'propagate': True,  # 向上(更高level的logger)傳遞
        },
    },
}


def load_my_logging_cfg():
    logging.config.dictConfig(LOGGING_DIC)  # 導入上面定義的logging配置
    logger = logging.getLogger(__name__)  # 生成一個log實例
    logger.info('It works!')  # 記錄該文件的運行狀態

if __name__ == '__main__':
    load_my_logging_cfg()
logging配置文件
#logging_config.py
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]'
                      '[%(levelname)s][%(message)s]'
        },
        'simple': {
            'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
        },
        'collect': {
            'format': '%(message)s'
        }
    },
    'filters': {
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {
        #打印到終端的日誌
        'console': {
            'level': 'DEBUG',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        #打印到文件的日誌,收集info及以上的日誌
        'default': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自動切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_info.log"),  # 日誌文件
            'maxBytes': 1024 * 1024 * 5,  # 日誌大小 5M
            'backupCount': 3,
            'formatter': 'standard',
            'encoding': 'utf-8',
        },
        #打印到文件的日誌:收集錯誤及以上的日誌
        'error': {
            'level': 'ERROR',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自動切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_err.log"),  # 日誌文件
            'maxBytes': 1024 * 1024 * 5,  # 日誌大小 5M
            'backupCount': 5,
            'formatter': 'standard',
            'encoding': 'utf-8',
        },
        #打印到文件的日誌
        'collect': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自動切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_collect.log"),
            'maxBytes': 1024 * 1024 * 5,  # 日誌大小 5M
            'backupCount': 5,
            'formatter': 'collect',
            'encoding': "utf-8"
        }
    },
    'loggers': {
        #logging.getLogger(__name__)拿到的logger配置
        '': {
            'handlers': ['default', 'console', 'error'],
            'level': 'DEBUG',
            'propagate': True,
        },
        #logging.getLogger('collect')拿到的logger配置
        'collect': {
            'handlers': ['console', 'collect'],
            'level': 'INFO',
        }
    },
}


# -----------
# 用法:拿到倆個logger

logger = logging.getLogger(__name__) #線上正常的日誌
collect_logger = logging.getLogger("collect") #領導說,須要爲領導們單獨定製領導們看的日誌
另一個Django的配置文件,瞭解
注意
有了上述方式咱們的好處是:全部與logging模塊有關的配置都寫到字典中就能夠了,更加清晰,方便管理


咱們須要解決的問題是:
一、從字典加載配置:logging.config.dictConfig(settings.LOGGING_DIC)
二、拿到logger對象來產生日誌
logger對象都是配置到字典的loggers 鍵對應的子字典中的
按照咱們對logging模塊的理解,要想獲取某個東西都是經過名字,也就是key來獲取的
因而咱們要獲取不一樣的logger對象就是
logger=logging.getLogger('loggers子字典的key名')

Logger類歷來不要直接構造一個實例,它都是經過模塊級別的函數logging.getLogger(name)來獲取到實例,當屢次調用時提供同樣的名稱,老是返回一個實例
    
    但問題是:若是咱們想要不一樣logger名的logger對象都共用一段配置,那麼確定不能在loggers子字典中定義n個key   
 'loggers': {    
        'l1': {
            'handlers': ['default', 'console'],  #
            'level': 'DEBUG',
            'propagate': True,  # 向上(更高level的logger)傳遞
        },
        'l2: {
            'handlers': ['default', 'console' ], 
            'level': 'DEBUG',
            'propagate': False,  # 向上(更高level的logger)傳遞
        },
        'l3': {
            'handlers': ['default', 'console'],  #
            'level': 'DEBUG',
            'propagate': True,  # 向上(更高level的logger)傳遞
        },

}
  
咱們的解決方式是,定義一個空的key
    'loggers': {
        '': {
            'handlers': ['default', 'console'], 
            'level': 'DEBUG',
            'propagate': True, 
        },

}

這樣咱們再取logger對象時
logging.getLogger(__name__),不一樣的文件__name__不一樣,這保證了打印日誌時標識信息不一樣,可是拿着該名字去loggers裏找key名時卻發現找不到,因而默認使用key=''的配置

使用logging會出現的bug

現象:

生產中心進行拷機任務下了300個任務,過了一陣時間後發現任務再也不被調度起來,查看後臺日誌發現日誌輸出停在某個時間點。

分析:
首先確認進程存在並無dead。
而後用strace –p看了一下進程,發現進程卡在futex調用上面,應該是在鎖操做上面出問題了。
用gdb attach進程ID,用py-bt查看一下堆棧,發現堆棧的信息大體爲:sig_handler(某個信號處理函數)->auroralogger(自定義的日誌函數)->logging(python的logging模塊)->threading.acquire(獲取鎖)。從gdb的bt信息基本驗證了上面的猜測,應該是出現了死鎖。
Python的logging模塊自己確定不會有死鎖的這種bug有可能出問題的就是咱們的使用方式,看python中logging模塊的doc,發現有一個有一個Thread
 Safety的章節,內容很簡單可是也一下就解釋了我遇到的這個問題,內容以下:


The logging module is intended to be thread-safe without any special work needing to be done by its clients. It achieves this though using threading
 locks; there is one lock to serialize access to the module’s shared data, and each handler also creates a lock to serialize access to its underlying I/O.

If you are implementing asynchronous signal handlers using the signal module,
 you may not be able to use logging from within such handlers. This is because lock implementations in the threading module
 are not always re-entrant, and so cannot be invoked from such signal handlers.


第一部分是說logging是線程安全的,經過threading的lock對公用的數據進行了加鎖。
第二部分特地提到了在異步的信號處理函數中不能使用logging模塊,由於threading的lock機制是不支持重入的。
這樣就解釋了上面我遇到的死鎖問題,由於我在信號處理函數中調用了不能夠重入的logging模塊。
線程安全和可重入:
      
從上面的logging模塊來看線程安全和可重入不是等價的,那麼這兩個概念之間有什麼聯繫、區別呢?

可重入函數:從字面意思來理解就是這個函數能夠重複調用,函數被多個線程亂序執行甚至交錯執行都能保證函數的輸出和函數單獨被執行一次的輸出一致。也就是說函數的輸出只決定於輸入。
線程安全函數:函數能夠被多個線程調用,而且保證不會引用到錯誤的或者髒的數據。線程安全的函數輸出不只僅依賴於輸入還可能依賴於被調用時的順序。

可重入函數和線程安全函數之間有一個最大的差別是:是不是異步信號安全。可重入函數在異步信號處理函數中能夠被安全調用,而線程安全函數不保證能夠在異步信號處理函數中被安全調用。

上面咱們遇到的loggin模塊就是非異步信號安全的,在主線程中咱們正在使用log函數而log函數調用了threading.lock來獲取到了鎖,此時一個異步信號產生程序跳轉到信號處理函數中,信號處理函數又正好調用了log函數,由於前一個被調用的log函數還未釋放鎖,最後就造成了一個死鎖。

可重入函數必然是線程安全函數和異步信號安全函數,線程安全函數不必定是可重入函數。

總結:     
異步信號處理函數中必定要儘量的功能簡單而且不能調用不可重入的函數。    
Python loggin模塊是線程安全可是是不可重入的。 

re

什麼是正則?

正則就是用一些具備特殊含義的符號組合到一塊兒(稱爲正則表達式)來描述字符或者字符串的方法。或者說:正則就是用來描述一類事物的規則。(在Python中)它內嵌在Python中,並經過 re 模塊實現。正則表達式模式被編譯成一系列的字節碼,而後由用 C 編寫的匹配引擎執行。

r"""Support for regular expressions (RE).

This module provides regular expression matching operations similar to
those found in Perl.  It supports both 8-bit and Unicode strings; both
the pattern and the strings being processed can contain null bytes and
characters outside the US ASCII range.

Regular expressions can contain both special and ordinary characters.
Most ordinary characters, like "A", "a", or "0", are the simplest
regular expressions; they simply match themselves.  You can
concatenate ordinary characters, so last matches the string 'last'.

The special characters are:
    "."      Matches any character except a newline.
    "^"      Matches the start of the string.
    "$"      Matches the end of the string or just before the newline at
             the end of the string.
    "*"      Matches 0 or more (greedy) repetitions of the preceding RE.
             Greedy means that it will match as many repetitions as possible.
    "+"      Matches 1 or more (greedy) repetitions of the preceding RE.
    "?"      Matches 0 or 1 (greedy) of the preceding RE.
    *?,+?,?? Non-greedy versions of the previous three special characters.
    {m,n}    Matches from m to n repetitions of the preceding RE.
    {m,n}?   Non-greedy version of the above.
    "\\"     Either escapes special characters or signals a special sequence.
    []       Indicates a set of characters.
             A "^" as the first character indicates a complementing set.
    "|"      A|B, creates an RE that will match either A or B.
    (...)    Matches the RE inside the parentheses.
             The contents can be retrieved or matched later in the string.
    (?aiLmsux) Set the A, I, L, M, S, U, or X flag for the RE (see below).
    (?:...)  Non-grouping version of regular parentheses.
    (?P<name>...) The substring matched by the group is accessible by name.
    (?P=name)     Matches the text matched earlier by the group named name.
    (?#...)  A comment; ignored.
    (?=...)  Matches if ... matches next, but doesn't consume the string.
    (?!...)  Matches if ... doesn't match next.
    (?<=...) Matches if preceded by ... (must be fixed length).
    (?<!...) Matches if not preceded by ... (must be fixed length).
    (?(id/name)yes|no) Matches yes pattern if the group with id/name matched,
                       the (optional) no pattern otherwise.

The special sequences consist of "\\" and a character from the list
below.  If the ordinary character is not on the list, then the
resulting RE will match the second character.
    \number  Matches the contents of the group of the same number.
    \A       Matches only at the start of the string.
    \Z       Matches only at the end of the string.
    \b       Matches the empty string, but only at the start or end of a word.
    \B       Matches the empty string, but not at the start or end of a word.
    \d       Matches any decimal digit; equivalent to the set [0-9] in
             bytes patterns or string patterns with the ASCII flag.
             In string patterns without the ASCII flag, it will match the whole
             range of Unicode digits.
    \D       Matches any non-digit character; equivalent to [^\d].
    \s       Matches any whitespace character; equivalent to [ \t\n\r\f\v] in
             bytes patterns or string patterns with the ASCII flag.
             In string patterns without the ASCII flag, it will match the whole
             range of Unicode whitespace characters.
    \S       Matches any non-whitespace character; equivalent to [^\s].
    \w       Matches any alphanumeric character; equivalent to [a-zA-Z0-9_]
             in bytes patterns or string patterns with the ASCII flag.
             In string patterns without the ASCII flag, it will match the
             range of Unicode alphanumeric characters (letters plus digits
             plus underscore).
             With LOCALE, it will match the set [0-9_] plus characters defined
             as letters for the current locale.
    \W       Matches the complement of \w.
    \\       Matches a literal backslash.

This module exports the following functions:
    match     Match a regular expression pattern to the beginning of a string.
    fullmatch Match a regular expression pattern to all of a string.
    search    Search a string for the presence of a pattern.
    sub       Substitute occurrences of a pattern found in a string.
    subn      Same as sub, but also return the number of substitutions made.
    split     Split a string by the occurrences of a pattern.
    findall   Find all occurrences of a pattern in a string.
    finditer  Return an iterator yielding a match object for each match.
    compile   Compile a pattern into a RegexObject.
    purge     Clear the regular expression cache.
    escape    Backslash all non-alphanumerics in a string.

Some of the functions in this module takes flags as optional parameters:
    A  ASCII       For string patterns, make \w, \W, \b, \B, \d, \D
                   match the corresponding ASCII character categories
                   (rather than the whole Unicode categories, which is the
                   default).
                   For bytes patterns, this flag is the only available
                   behaviour and needn't be specified.
    I  IGNORECASE  Perform case-insensitive matching.
    L  LOCALE      Make \w, \W, \b, \B, dependent on the current locale.
    M  MULTILINE   "^" matches the beginning of lines (after a newline)
                   as well as the string.
                   "$" matches the end of lines (before a newline) as well
                   as the end of the string.
    S  DOTALL      "." matches any character at all, including the newline.
    X  VERBOSE     Ignore whitespace and comments for nicer looking RE's.
    U  UNICODE     For compatibility only. Ignored for string patterns (it
                   is the default), and forbidden for bytes patterns.

This module also defines an exception 'error'.

"""
python3.6 re相關文檔解釋

字符組:[0-9][a-z][A-Z]

在同一個位置可能出現的各類字符組成了一個字符組,在正則表達式中用[]表示,字符分爲不少類,好比數字、字母、標點等等。
假如你如今要求一個位置"只能出現一個數字",那麼這個位置上的字符只能是0、一、2...9這10個數之一。
能夠寫成這種 [0-5a-eA-Z] 取範圍的匹配

字符

 .   匹配除換行符之外的任意字符
\w  匹配字母或數字或下劃線
\s  匹配任意的空白符
\d  匹配數字
\n  匹配一個換行符
\t  匹配一個製表符
\b  匹配一個單詞的結尾
^   匹配字符串的開始
$   匹配字符串的結尾
\W  匹配非字母或數字或下劃線
\D  匹配非數字
\S  匹配非空白符
a|b 匹配字符a或字符b
()  匹配括號內的表達式,也表示一個組
[...]   匹配字符組中的字符
[^...]  匹配除了字符組中字符的全部字符

量詞

量詞  用法說明
*   重複零次或更屢次
+   重複一次或更屢次
?   重複零次或一次
{n} 重複n次
{n,}    重複n次或更屢次
{n,m}   重複n到m次

.^$

 

正則      待匹配字符       匹配結果           說明
東.       東方東嬌東東     東方東嬌東東        匹配全部"東."的字符
^東.      東方東嬌東東     東方               只從開頭匹配"東."
東.$      東方東嬌東東     東東               只匹配結尾的"東.$"

*+?{}

正則      待匹配字符                   匹配結果                說明
李.?     李傑和李蓮英和李二棍子     李傑/李蓮/李二              ?表示重複零次或一次,即只匹配"李"後面一個任意字符 
李.*     李傑和李蓮英和李二棍子     李傑和李蓮英和李二棍子        *表示重複零次或屢次,即匹配"李"後面0或多個任意字符
李.+     李傑和李蓮英和李二棍子     李傑和李蓮英和李二棍子        +表示重複一次或屢次,即只匹配"李"後面1個或多個任意字符
李.{1,2} 李傑和李蓮英和李二棍子     李傑和/李蓮英/李二棍         {1,2}匹配1到2次任意字符

注意:前面的*,+,?等都是貪婪匹配,也就是儘量匹配,後面加?號使其變成惰性匹配
正則      待匹配字符                   匹配結果        說明
李.*?     李傑和李蓮英和李二棍子       李/李/李         惰性匹配

字符集[][^]

正則                   待匹配字符                  匹配結果                說明
李[傑蓮英二棍子]*      李傑和李蓮英和李二棍子     李傑/李蓮英/李二棍子     表示匹配"李"字後面[傑蓮英二棍子]的字符任意次 
李[^和]*               李傑和李蓮英和李二棍子     李傑/李蓮英/李二棍子     表示匹配一個不是"和"的字符任意次
[\d]                   456bdha3                   4/5/6/3                  表示匹配任意一個數字,匹配到4個結果
[\d]+                  456bdha3                   456/3                    表示匹配任意個數字,匹配到2個結果

分組()或|和[^] 

身份證號碼是一個長度爲15或18個字符的字符串,若是是15位則所有是數字組成,首位不能爲0;若是是18位,則前17位所有是數字,末位多是數字或x,下面咱們嘗試用正則來表示:

正則                                 待匹配字符                   匹配結果                  說明
^[1-9]\d{13,16}[0-9x]$              110101198001017032          110101198001017032      表示能夠匹配一個正確的身份證號
^[1-9]\d{13,16}[0-9x]$              1101011980010170            1101011980010170        表示也能夠匹配這串數字,但這並非一個正確的身份證號碼,它是一個16位的數字
^[1-9]\d{14}(\d{2}[0-9x])?$         1101011980010170            False                   如今不會匹配錯誤的身份證號了()表示分組,將\d{2}[0-9x]分紅一組,就能夠總體約束他們出現的次數爲0-1次
^([1-9]\d{16}[0-9x]|[1-9]\d{14})$   110105199812067023          110105199812067023      表示先匹配[1-9]\d{16}[0-9x]若是沒有匹配上就匹配[1-9]\d{14} 

轉義符\

在正則表達式中,有不少有特殊意義的是元字符,好比\d和\s等,若是要在正則中匹配正常的"\d"而不是"數字"就須要對"\"進行轉義,變成'\\'。
在python中,不管是正則表達式,仍是待匹配的內容,都是以字符串的形式出現的,在字符串中\也有特殊的含義,自己還須要轉義。因此若是匹配一次"\d",字符串中要寫成'\\d',那麼正則裏就要寫成"\\\\d",這樣就太麻煩了。
這個時候咱們就用到了r'\d'這個概念,此時的正則是r'\\d'就能夠了。
正則                   待匹配字符                  匹配結果                說明
d                      \d                         False                   由於在正則表達式中\是有特殊意義的字符,因此要匹配\d自己,用表達式\d沒法匹配
\\d                    \d                          True                    轉義\以後變成\\,便可匹配
"\\\\d"                '\\d'                       True                    若是在python中,字符串中的'\'也須要轉義,因此每個字符串'\'又須要轉義一次
r'\\d'                  r'\d'                     True                    在字符串以前加r,讓整個字符串不轉義

貪婪匹配

re模塊在py中的使用

 

1.compile
re.compile(pattern[, flags])
做用:把正則表達式語法轉化成正則表達式對象

obj = re.compile('\d{3}')  #將正則表達式編譯成爲一個 正則表達式對象,規則要匹配的是3個數字
ret = obj.search('abc123eeee') #正則表達式對象調用search,參數爲待匹配的字符串
print(ret.group())  #結果 : 123

flags定義包括:
re.I:忽略大小寫
re.L:表示特殊字符集 \w, \W, \b, \B, \s, \S 依賴於當前環境
re.M:多行模式
re.S:' . '而且包括換行符在內的任意字符(注意:' . '不包括換行符)
re.U: 表示特殊字符集 \w, \W, \b, \B, \d, \D, \s, \S 依賴於 Unicode 字符屬性數據庫

多行匹配的例子
import re

line = "IF_MIB::=Counter32: 12345\nIF_MIB::=Counter32: 1234556";
result = re.findall(r'(?<=\:\s)\d+$', line, re.M)

if result:
    print(result)
else:
    print("Nothing found!!")

'''不加re.M
輸出['1234556']
    加上re.M
輸出['12345', '1234556']
'''

2.search
re.search(pattern, string[, flags])

ret = re.search('a', 'eva egon yuan').group()
print(ret) #結果 : 'a'
# 函數會在字符串內查找模式匹配,只到找到第一個匹配而後返回一個包含匹配信息的對象,該對象能夠經過調用group()方法獲得匹配的字符串,若是字符串沒有匹配,則返回None。

做用:在字符串中查找匹配正則表達式模式的位置,返回 MatchObject 的實例,若是沒有找到匹配的位置,則返回 None。

3.match
re.match(pattern, string[, flags])
match(string[, pos[, endpos]])
做用:match() 函數只在字符串的開始位置嘗試匹配正則表達式,也就是隻報告從位置 0 開始的匹配狀況,
而 search() 函數是掃描整個字符串來查找匹配。若是想要搜索整個字符串來尋找匹配,應當用 search()

ret = re.match('a', 'abc').group()  # 同search,不過只在字符串開始處進行匹配
print(ret)
#結果 : 'a'


例子:
import re
r1 = re.compile(r'world')
if r1.match('helloworld'):
    print 'match succeeds'
else:
    print 'match fails'
if r1.search('helloworld'):
    print 'search succeeds'
else:
    print 'search fails'
結果
#match fails
#search succeeds


4.split
re.split(pattern, string[, maxsplit=0, flags=0])
split(string[, maxsplit=0])
做用:能夠將字符串匹配正則表達式的部分割開並返回一個列表

import re
inputStr = 'abc aa;bb,cc | dd(xx).xxx 12.12';
print(re.split(' ',inputStr))
結果
#['abc', 'aa;bb,cc', '|', 'dd(xx).xxx', '12.12']

5.findall
re.findall(pattern, string[, flags])
findall(string[, pos[, endpos]])

ret = re.findall('a', 'eva egon yuan')  # 返回全部知足匹配條件的結果,放在列表裏
print(ret) #結果 : ['a', 'a']
做用:在字符串中找到正則表達式所匹配的全部子串,並組成一個列表返回
例:查找[]包括的內容(貪婪和非貪婪查找)

6.finditer
re.finditer(pattern, string[, flags])
finditer(string[, pos[, endpos]])
說明:和 findall 相似,在字符串中找到正則表達式所匹配的全部子串,並組成一個迭代器返回。

ret = re.finditer('\d', 'ds3sy4784a')   #finditer返回一個存放匹配結果的迭代器
print(ret)  # <callable_iterator object at 0x10195f940>
print(next(ret).group())  #查看第一個結果
print(next(ret).group())  #查看第二個結果
print([i.group() for i in ret])  #查看剩餘的左右結果


7.sub
re.sub(pattern, repl, string[, count, flags])
sub(repl, string[, count=0])
說明:在字符串 string 中找到匹配正則表達式 pattern 的全部子串,用另外一個字符串 repl 進行替換。若是沒有找到匹配 pattern 的串,則返回未被修改的 string。
Repl 既能夠是字符串也能夠是一個函數。

import re
def pythonReSubDemo():
    inputStr = "hello 123,my 234,world 345"
    def _add111(matched):
        intStr = int(matched.group("number"))
        _addValue = intStr + 111;
        _addValueStr = str(_addValue)
        return _addValueStr

    replaceStr = re.sub("(?P<number>\d+)",_add111,inputStr,1)
    print("replaceStr=",replaceStr)

if __name__ == '__main__':
    pythonReSubDemo();
##############
#hello 234,my 234,world 345


注意:
#1 findall的優先級查詢
import re

ret = re.findall('www.(baidu|oldboy).com', 'www.oldboy.com')
print(ret)  # ['oldboy']     這是由於findall會優先把匹配結果組裏內容返回,若是想要匹配結果,取消權限便可

ret = re.findall('www.(?:baidu|oldboy).com', 'www.oldboy.com')
print(ret)  # ['www.oldboy.com']

#2 split的優先級查詢
ret=re.split("\d+","eva3egon4yuan")
print(ret) #結果 : ['eva', 'egon', 'yuan']

ret=re.split("(\d+)","eva3egon4yuan")
print(ret) #結果 : ['eva', '3', 'egon', '4', 'yuan']

#在匹配部分加上()以後所切出的結果是不一樣的,
#沒有()的沒有保留所匹配的項,可是有()的卻可以保留了匹配的項,
#這個在某些須要保留匹配部分的使用過程是很是重要的。

#findall  #直接返回一個列表
#正常的正則表達式
#可是隻會把分組裏的顯示出來

#search   #返回一個對象 .group()
#match    #返回一個對象 .group()

 

  

#!/usr/bin/python env
#_*_coding:utf-8_*_
 
貪婪匹配:在知足匹配時,匹配儘量長的字符串,默認狀況下,採用貪婪匹配
正則                   待匹配字符                  匹配結果                說明
<.*>                   <script>...<script>         <script>...<script>     默認爲貪婪匹配模式,會匹配儘可能長的字符串
<.*?>                   r'\d'                       <script>/<script>      加上?爲將貪婪匹配模式轉爲非貪婪匹配模式,會匹配儘可能短的字符串

幾個經常使用的非貪婪匹配Pattern
*? 重複任意次,但儘量少重複
+? 重複1次或更屢次,但儘量少重複
?? 重複0次或1次,但儘量少重複
{n,m}? 重複n到m次,但儘量少重複
{n,}? 重複n次以上,但儘量少重複

.*?的用法
. 是任意字符
* 是取 0 至 無限長度
? 是非貪婪模式。
何在一塊兒就是 取儘可能少的任意字符,通常不會這麼單獨寫,他大多用在:
.*?x

就是取前面任意長度的字符,直到一個x出現
View Code
 1 #!/usr/bin/python env
 2 #_*_coding:utf-8_*_
 3 
 4 #一、匹配標籤
 5 import re
 6 ret = re.search("<(?P<tag_name>\w+)>\w+</(?P=tag_name)>","<h1>hello</h1>")
 7 #還能夠在分組中利用?<name>的形式給分組起名字
 8 #獲取的匹配結果能夠直接用group('名字')拿到對應的值
 9 print(ret.group('tag_name'))  #結果 :h1
10 print(ret.group())  #結果 :<h1>hello</h1>
11 
12 ret = re.search(r"<(\w+)>\w+</\1>","<h1>hello</h1>")
13 #若是不給組起名字,也能夠用\序號來找到對應的組,表示要找的內容和前面的組內容一致
14 #獲取的匹配結果能夠直接用group(序號)拿到對應的值
15 print(ret.group(1))
16 print(ret.group())  #結果 :<h1>hello</h1>
17 
18 #二、匹配整數
19 import re
20 ret=re.findall(r"\d+","1-2*(60+(-40.35/5)-(-4*3))")
21 print(ret) #['1', '2', '60', '40', '35', '5', '4', '3']
22 ret=re.findall(r"-?\d+\.\d*|(-?\d+)","1-2*(60+(-40.35/5)-(-4*3))")
23 print(ret) #['1', '-2', '60', '', '5', '-4', '3']
24 ret.remove("")
25 print(ret) #['1', '-2', '60', '5', '-4', '3']
26 
27 #三、數字匹配
28 # 一、 匹配一段文本中的每行的郵箱
29 #       http://blog.csdn.net/make164492212/article/details/51656638
30 # 二、 匹配一段文本中的每行的時間字符串,好比:‘1990-07-12’;
31 #
32 #    分別取出1年的12個月(^(0?[1-9]|1[0-2])$)、
33 #    一個月的31天:^((0?[1-9])|((1|2)[0-9])|30|31)$
34 # 三、 匹配qq號。(騰訊QQ號從10000開始)  [1,9][0,9]{4,}
35 # 四、 匹配一個浮點數。       ^(-?\d+)(\.\d+)?$   或者  -?\d+\.?\d*
36 # 五、 匹配漢字。             ^[\u4e00-\u9fa5]{0,}$
37 # 六、 匹配出全部整數
re習題
 1 #!/usr/bin/python env
 2 #_*_coding:utf-8_*_
 3 import re
 4 import json
 5 import requests  #urlopen
 6 
 7 def getPage(url):
 8     response = requests.get(url)
 9     return response.text
10 
11 def parsePage(s):
12     com = re.compile(
13         '<div class="item">.*?<div class="pic">.*?<em .*?>(?P<id>\d+).*?<span class="title">(?P<title>.*?)</span>'
14         '.*?<span class="rating_num" .*?>(?P<rating_num>.*?)</span>.*?<span>(?P<comment_num>.*?)評價</span>', re.S)
15 
16     ret = com.finditer(s)
17     for i in ret:
18         yield {
19             "id": i.group("id"),
20             "title": i.group("title"),
21             "rating_num": i.group("rating_num"),
22             "comment_num": i.group("comment_num"),
23         }
24 
25 def main(num):
26     url = 'https://movie.douban.com/top250?start=%s&filter=' % num
27     response_html = getPage(url)
28     ret = parsePage(response_html)
29     print(ret)
30     f = open("move_info7", "a", encoding="utf8")
31 
32     for obj in ret:  #循環生成器
33         print(obj)
34         data = json.dumps(obj, ensure_ascii=False)
35         f.write(data + "\n")
36 
37 count = 0
38 for i in range(10):
39     main(count)
40     count += 25
爬豆瓣網頁匹配

collection

在內置數據類型(dict、list、set、tuple)的基礎上,collections模塊還提供了幾個額外的數據類型:Counter、deque、defaultdict、namedtuple和OrderedDict等。

namedtuple: 生成可使用名字來訪問元素內容的tuple

deque: 雙端隊列,能夠快速的從另一側追加和推出對象

Counter: 計數器,主要用來計數

OrderedDict: 有序字典

defaultdict: 帶有默認值的字典
View Code

namedtuple

們知道tuple能夠表示不變集合,例如,一個點的二維座標就能夠表示成:

>>> p = (1, 2)
View Code

可是,看到(1, 2),很難看出這個tuple是用來表示一個座標的。

這時,namedtuple就派上了用場:

>>> from collections import namedtuple
>>> Point = namedtuple('Point', ['x', 'y'])
>>> p = Point(1, 2)
>>> p.x
1
>>> p.y
2
View Code

似的,若是要用座標和半徑表示一個圓,也能夠用namedtuple定義:

#namedtuple('名稱', [屬性list]):
Circle = namedtuple('Circle', ['x', 'y', 'r'])
View Code

deque

使用list存儲數據時,按索引訪問元素很快,可是插入和刪除元素就很慢了,由於list是線性存儲,數據量大的時候,插入和刪除效率很低。

deque是爲了高效實現插入和刪除操做的雙向列表,適合用於隊列和棧:

>>> from collections import deque
>>> q = deque(['a', 'b', 'c'])
>>> q.append('x')
>>> q.appendleft('y')
>>> q
deque(['y', 'a', 'b', 'c', 'x'])
View Code

deque除了實現list的append()pop()外,還支持appendleft()popleft(),這樣就能夠很是高效地往頭部添加或刪除元素。

OrderedDict

使用dict時,Key是無序的。在對dict作迭代時,咱們沒法肯定Key的順序。

若是要保持Key的順序,能夠用OrderedDict

>>> from collections import OrderedDict
>>> d = dict([('a', 1), ('b', 2), ('c', 3)])
>>> d # dict的Key是無序的
{'a': 1, 'c': 3, 'b': 2}
>>> od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
>>> od # OrderedDict的Key是有序的
OrderedDict([('a', 1), ('b', 2), ('c', 3)])
View Code

意,OrderedDict的Key會按照插入的順序排列,不是Key自己排序:

>>> od = OrderedDict()
>>> od['z'] = 1
>>> od['y'] = 2
>>> od['x'] = 3
>>> od.keys() # 按照插入的Key的順序返回
['z', 'y', 'x']
View Code

defaultdict 

有以下值集合 [11,22,33,44,55,66,77,88,99,90...],將全部大於 66 的值保存至字典的第一個key中,將小於 66 的值保存至第二個key的值中。

即: { 'k1' : 大於 66  'k2' : 小於 66 }
values = [11, 22, 33,44,55,66,77,88,99,90]

my_dict = {}

for value in  values:
    if value>66:
        if my_dict.has_key('k1'):
            my_dict['k1'].append(value)
        else:
            my_dict['k1'] = [value]
    else:
        if my_dict.has_key('k2'):
            my_dict['k2'].append(value)
        else:
            my_dict['k2'] = [value]
View Code
from collections import defaultdict

values = [11, 22, 33,44,55,66,77,88,99,90]

my_dict = defaultdict(list)

for value in  values:
    if value>66:
        my_dict['k1'].append(value)
    else:
        my_dict['k2'].append(value)
View Code

使dict時,若是引用的Key不存在,就會拋出KeyError。若是但願key不存在時,返回一個默認值,就能夠用defaultdict

>>> from collections import defaultdict
>>> dd = defaultdict(lambda: 'N/A')
>>> dd['key1'] = 'abc'
>>> dd['key1'] # key1存在
'abc'
>>> dd['key2'] # key2不存在,返回默認值
'N/A'
View Code

Counter

Counter類的目的是用來跟蹤值出現的次數。它是一個無序的容器類型,以字典的鍵值對形式存儲,其中元素做爲key,其計數做爲value。計數值能夠是任意的Interger(包括0和負數)。Counter類和其餘語言的bags或multisets很類似。

建立

下面的代碼說明了Counter類建立的四種方法:

計數值的訪問與缺失的鍵

當所訪問的鍵不存在時,返回0,而不是KeyError;不然返回它的計數。

計數器的更新(update和subtract)

可使用一個iterable對象或者另外一個Counter對象來更新鍵值。

計數器的更新包括增長和減小兩種。其中,增長使用update()方法:

減小則使用subtract()方法:

鍵的修改和刪除

當計數值爲0時,並不意味着元素被刪除,刪除元素應當使用del。 

elements()

返回一個迭代器。元素被重複了多少次,在該迭代器中就包含多少個該元素。元素排列無肯定順序,個數小於1的元素不被包含。

most_common([n])

返回一個TopN列表。若是n沒有被指定,則返回全部元素。當多個元素計數值相同時,排列是無肯定順序的。

算術和集合操做

+、-、&、|操做也能夠用於Counter。其中&和|操做分別返回兩個Counter對象各元素的最小值和最大值。須要注意的是,獲得的Counter對象將刪除小於1的元素。

其餘經常使用操做

下面是一些Counter類的經常使用操做,來源於Python官方文檔

相關文章
相關標籤/搜索