Python re 庫的正確使用姿勢

浮生若夢的程式設計發表於2017-12-11

前提假設:

  1. 已經充分掌握 PCRE 風格正規表示式
  2. 熟讀 re 庫文件

Why

正規表示式的強大已不用我贅述,Python 對此的支援也是十分強大,只不過:

re.search(pattern, string, flags=0)
re.match(pattern, string, flags=0)
......
複製程式碼

你能很麻利地使用如上所示的一系列模組級別function 嗎,如果你天天用 Python 搞正則匹配,相信你一定很熟練。但是如果你需要每次臨時翻閱文件才能知道如何使用它,那麼就要思考:是不是 API 在某種程度上設計不好了(有的語言的 pattern 極有可能不是放在首位)。

一般來說,API 的介面引數越少越好,最好的就是沒有引數,呼叫者無腦呼叫,沒有任何記憶負擔。而 Python 的 re 庫,在我看來,應該至少糅合了「命令式」與「OOP」兩種風格,而且介面也不「最小化,正交」。

使用姿勢

正確的姿勢應該是:只用 OOP 風格,並且完全忘記 re 庫提供的一系列模組級別的 function (如 re.search, re.match等)。

首先是每次都構造出 Regex 物件,然後由 Regex 物件得出 Match 物件,然後在 Regex 物件和 Match 物件上進行一系列操作。比如:

# 1. 構造
    REGEX = re.compile($pattern, flags)     flags是re模組的常量

# 2. 獲取 MatchObject
    m = regex.search(string)
   
# 3. 後續 MatchObject 的使用
    1. 獲取分組  group()    
    2. groups
    3. groupdict()
複製程式碼

應用舉例

比如我在自己構造的 PathUtils 中,就是如此使用的(我非常喜歡各種各樣的 Utils ):

from __future__ import (absolute_import, unicode_literals)

import re

class PathUtils(object):
    """路徑操作的工具函式"""

    _LINUX_ROOT = '/'
    _LINUX_PATH_SPLITOR = '/'

    @classmethod
    def is_two_linux_path_contains(cls, path1, path2):
        """兩個Linux路徑是否存在互相包含關係"""

        if path1 == cls._LINUX_ROOT or path2 == cls._LINUX_ROOT:
            return True

        path1_split = path1.split(cls._LINUX_PATH_SPLITOR)
        path2_split = path2.split(cls._LINUX_PATH_SPLITOR)

        for item1, item2 in zip(path1_split, path2_split):
            if item1 != item2:
                return False

        return True

    @classmethod
    def is_valid_linux_path(cls, path):
        if not path:
            return False

        LINUX_PATH_REGEX = r'^(/[^/ ]*)+/?$'

        return cls.is_valid_pattern(path, LINUX_PATH_REGEX)

    @classmethod
    def is_valid_windows_path(cls, path):
        if not path:
            return False

        WINDOWS_PATH_REGEX = r'^[a-zA-Z]:\\(((?![<>:"/\\|?*]).)+((?<![ .])\\)?)*$'

        return cls.is_valid_pattern(path, WINDOWS_PATH_REGEX)

    @classmethod
    def is_valid_path(cls, p):
        if not p:
            return False

        return cls.is_valid_linux_path(p) or cls.is_valid_windows_path(p)

    @classmethod
    def is_valid_pattern(cls, value, pattern):
        if not value:
            return False

        REGEX = re.compile(pattern, re.UNICODE)

        m = REGEX.match(value)

        return True if m else False
複製程式碼

主要的功能函式就是:

@classmethod
def is_valid_pattern(cls, value, pattern):
    if not value:
        return False

    REGEX = re.compile(pattern, re.UNICODE)

    m = REGEX.match(value)

    return True if m else False
複製程式碼

這樣一系列流程下來,我的感受就是,re 庫的介面沒有需要記憶,也沒有需要臨時翻閱文件的地方,並且我只用這一種風格(自己熟悉的,效率總是最高的),比如 re.compile肯定只需要傳一個引數(flags不是必要的),REGEX_OBJ.match/search肯定只需要傳need_search_string即可。

附錄

相關文章