1. 程式人生 > >常用模組之hashlib,subprocess,logging,re,collections

常用模組之hashlib,subprocess,logging,re,collections

一 、hashlib

1、什麼叫hash:hash是一種演算法(3.x裡代替了md5模組和sha模組,主要提供 SHA1, SHA224, SHA256, SHA384, SHA512 ,MD5 演算法),該演算法接受傳入的內容,經過運算得到一串hash值
2、hash值的特點是:
 2.1 只要傳入的內容一樣,得到的hash值必然一樣=====>要用明文傳輸密碼檔案完整性校驗
 2.2 不能由hash值返解成內容=======》把密碼做成hash值,不應該在網路傳輸明文密碼
 2.3 只要使用的hash演算法不變,無論校驗的內容有多大,得到的hash值長度是固定的
什麼是hash

什麼是摘要演算法呢?摘要演算法又稱雜湊演算法、雜湊演算法。它通過一個函式,把任意長度的資料轉換為一個長度固定的資料串(通常用16進位制的字串表示)。

摘要演算法就是通過摘要函式f()對任意長度的資料data計算出固定長度的摘要digest,目的是為了發現原始資料是否被人篡改過。

摘要演算法之所以能指出資料是否被篡改過,就是因為摘要函式是一個單向函式,計算f(data)很容易,但通過digest反推data卻非常困難。而且,對原始資料做一個bit的修改,都會導致計算出的摘要完全不同。

我們以常見的摘要演算法MD5為例,計算出一個字串的MD5值:

import
hashlib md5 = hashlib.md5() md5.update('how to use md5 in python hashlib?') print md5.hexdigest() 計算結果如下: d26a53750bc40b38b65a520292f69306
View Code

如果資料量很大,可以分塊多次呼叫update(),最後計算的結果是一樣的:

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

 

 以上加密演算法雖然依然非常厲害,但時候存在缺陷,即:通過撞庫可以反解。所以,有必要對加密演算法中新增自定義key再來做加密。

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 和 內容 進行進一步的處理然後再加密

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

import  subprocess

'''
sh-3.2# ls /Users/egon/Desktop |grep txt$
mysql.txt
tt.txt
事物.txt
'''

res1=subprocess.Popen('ls /Users/jieli/Desktop',shell=True,stdout=subprocess.PIPE)
res=subprocess.Popen('grep txt$',shell=True,stdin=res1.stdout,
                 stdout=subprocess.PIPE)

print(res.stdout.read().decode('utf-8'))


#等同於上面,但是上面的優勢在於,一個數據流可以和另外一個數據流互動,可以通過爬蟲得到結果然後交給grep
res1=subprocess.Popen('ls /Users/jieli/Desktop |grep txt$',shell=True,stdout=subprocess.PIPE)
print(res1.stdout.read().decode('utf-8'))


#windows下:
# dir | findstr 'test*'
# dir | findstr 'txt$'
import subprocess
res1=subprocess.Popen(r'dir C:\Users\Administrator\PycharmProjects\test\函式備課',shell=True,stdout=subprocess.PIPE)
res=subprocess.Popen('findstr test*',shell=True,stdin=res1.stdout,
                 stdout=subprocess.PIPE)
 
print(res.stdout.read().decode('gbk')) #subprocess使用當前系統預設編碼,得到結果為bytes型別,在windows下需要用gbk解碼
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
View Code

 logging模組的Formatter,Handler,Logger,Filter物件

logger:產生日誌的物件

Filter:過濾日誌的物件

Handler:接收日誌然後控制列印到不同的地方,FileHandler用來列印到檔案中,StreamHandler用來列印到終端

Formatter物件:可以定製不同的日誌格式物件,然後繫結給不同的Handler物件使用,以此來控制不同的Handler的日誌格式
View Code
'''
critical=50
error =40
warning =30
info = 20
debug =10
'''

import logging

1、logger物件:負責產生日誌,然後交給Filter過濾,然後交給不同的Handler輸出
logger=logging.getLogger(__file__)

2、Filter物件:不常用,略

3、Handler物件:接收logger傳來的日誌,然後控制輸出
h1=logging.FileHandler('t1.log') #列印到檔案
h2=logging.FileHandler('t2.log') #列印到檔案
h3=logging.StreamHandler() #列印到終端

#4、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',)


5、為Handler物件繫結格式
h1.setFormatter(formmater1)
h2.setFormatter(formmater2)
h3.setFormatter(formmater3)

6、將Handler新增給logger並設定日誌級別
logger.addHandler(h1)
logger.addHandler(h2)
logger.addHandler(h3)
logger.setLevel(10)

7、測試
logger.debug('debug')
logger.info('info')
logger.warning('warning')
logger.error('error')
logger.critical('critical')
View Code

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配置檔案

 

"""
MyLogging Test
"""

import time
import logging
import my_logging  # 匯入自定義的logging配置

logger = logging.getLogger(__name__)  # 生成logger例項


def demo():
    logger.debug("start range... time:{}".format(time.time()))
    logger.info("中文測試開始。。。")
    for i in range(10):
        logger.debug("i:{}".format(i))
        time.sleep(0.2)
    else:
        logger.debug("over range... time:{}".format(time.time()))
    logger.info("中文測試結束。。。")

if __name__ == "__main__":
    my_logging.load_my_logging_cfg()  # 在你程式檔案的入口載入自定義logging配置
    demo()
使用

 

注意注意注意:


#1、有了上述方式我們的好處是:所有與logging模組有關的配置都寫到字典中就可以了,更加清晰,方便管理


#2、我們需要解決的問題是:
    1、從字典載入配置:logging.config.dictConfig(settings.LOGGING_DIC)

    2、拿到logger物件來產生日誌
    logger物件都是配置到字典的loggers 鍵對應的子字典中的
    按照我們對logging模組的理解,要想獲取某個東西都是通過名字,也就是key來獲取的
    於是我們要獲取不同的logger物件就是
    logger=logging.getLogger('loggers子字典的key名')

    
    但問題是:如果我們想要不同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=''的配置
關於如何拿到logger物件的詳細分析

 

#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的配置檔案,瞭解

四、 re

什麼是正則?

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

 

1.字元組:[0-9][a-z][A-Z]

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

2.字元

#!/usr/bin/python env
#_*_coding:utf-8_*_

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

3.量詞

#!/usr/bin/python env
#_*_coding:utf-8_*_
 
量詞  用法說明
*   重複零次或更多次
+   重複一次或更多次
?   重複零次或一次
{n} 重複n次
{n,}    重複n次或更多次
{n,m}   重複n到m次
View Code

4. .^$

#!/usr/bin/python env
#_*_coding:utf-8_*_
正則      待匹配字元       匹配結果           說明
海.       海燕海嬌海東     海燕海嬌海東        匹配所有"海."的字元
^海.      海燕海嬌海東     海燕               只從開頭匹配"海."
海.$      海燕海嬌海東     海東               只匹配結尾的"海.$"
View Code

5.*+?{}

#!/usr/bin/python env
#_*_coding:utf-8_*_
正則      待匹配字元                   匹配結果                說明
李.?     李傑和李蓮英和李二棍子     李傑/李蓮/李二              ?表示重複零次或一次,即只匹配""後面一個任意字元 
李.*     李傑和李蓮英和李二棍子     李傑和李蓮英和李二棍子        *表示重複零次或多次,即匹配""後面0或多個任意字元
李.+     李傑和李蓮英和李二棍子     李傑和李蓮英和李二棍子        +表示重複一次或多次,即只匹配""後面1個或多個任意字元
李.{1,2} 李傑和李蓮英和李二棍子     李傑和/李蓮英/李二棍         {1,2}匹配1到2次任意字元

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

6.字符集[][^]

#!/usr/bin/python env
#_*_coding:utf-8_*_

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

7.分組()或|和[^] 

#!/usr/bin/python env
#_*_coding:utf-8_*_

身份證號碼是一個長度為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}
View Code

8.轉義符\

#!/usr/bin/python env
#_*_coding:utf-8_*_

在正則表示式中,有很多有特殊意義的是元字元,比如\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,讓整個字串不轉義
View Code

9,貪婪匹配

#!/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

 

#findall  #直接返回一個列表
#正常的正則表示式
#但是隻會把分組裡的顯示出來
#search #返回一個物件 .group()
#match  #返回一個物件 .group()

import re
#re模組的用法

ret = re.findall('a', 'eva egon yuan')  # 返回所有滿足匹配條件的結果,放在列表裡
print(ret) #結果 : ['a', 'a']

ret = re.search('a', 'eva egon yuan').group()
print(ret) #結果 : 'a'
# 函式會在字串內查詢模式匹配,只到找到第一個匹配然後返回一個包含匹配資訊的物件,該物件可以
# 通過呼叫group()方法得到匹配的字串,如果字串沒有匹配,則返回None。

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

ret = re.split('[ab]', 'abcd')  # 先按'a'分割得到''和'bcd',在對''和'bcd'分別按'b'分割
print(ret)  # ['', '', 'cd']

ret = re.sub('\d', 'H', 'eva3egon4yuan4', 1)#將數字替換成'H',引數1表示只替換1個
print(ret) #evaHegon4yuan4

ret = re.subn('\d', 'H', 'eva3egon4yuan4')#將數字替換成'H',返回元組(替換的結果,替換了多少次)
print(ret)

obj = re.compile('\d{3}')  #將正則表示式編譯成為一個 正則表示式物件,規則要匹配的是3個數字
ret = obj.search('abc123eeee') #正則表示式物件呼叫search,引數為待匹配的字串
print(ret.group())  #結果 : 1