【问题标题】:Can a python dictionary use re.compile as a key?python 字典可以使用 re.compile 作为键吗?
【发布时间】:2017-01-23 11:39:18
【问题描述】:

我们有 10-20 个遵循这种基本格式的字典,键是要分配的值,值是正则表达式:

osTypeRE = collections.OrderedDict([
    ('WINDOWS', re.compile('^.*(windows|WIN2008|WIN2003).*$', re.MULTILINE+re.IGNORECASE)),
    ('LINUX/UNIX', re.compile('^.*(unix|linux|ubuntu|red hat|redhat|RHEL|CentOS|CENT OS|Debian|SLES|SUSE|freebsd|free bsd|AIX|Solaris|SunOS).*$', re.MULTILINE+re.IGNORECASE)),
    ('MAC', re.compile('^.*(mac os x).*$', re.MULTILINE+re.IGNORECASE)),
    ('STRATUS VOS', re.compile('^.*(VOS|Stratus).*$', re.MULTILINE+re.IGNORECASE)),
    ('MAINFRAME', re.compile('^.*(OS400|AS400).*$', re.MULTILINE+re.IGNORECASE)),
    ('CISCO IOS', re.compile('^.*(IOS).*$', re.MULTILINE+re.IGNORECASE)),
    ('NETWARE/OES', re.compile('^.*(NETWARE|OES|Open Enterprise Server).*$', re.MULTILINE+re.IGNORECASE)),
    ('OPENVMS', re.compile('^.*(VMS).*$', re.MULTILINE+re.IGNORECASE)),
    ('HYPERVISOR', re.compile('^.*(ESX).*$', re.MULTILINE+re.IGNORECASE)),
    ('HP NONSTOP', re.compile('^.*(NONSTOP|Tandem|NON STOP).*|.*(H06.20).*$', re.MULTILINE+re.IGNORECASE)),
    ('EMBEDDED', re.compile('^.*(QNX).*$', re.MULTILINE+re.IGNORECASE))
])

事实证明,这些类型的字典非常有用,因为它使我们能够将来自多个管理系统的计算机信息标准化,以便随后对信息进行协调。

唯一的问题是它太慢了。这是我们使用这种类型的字典来规范化我们的数据的函数:

def reg_lookup(lookup, re_dict):
    value = "INDETERMINATE"
    for key, reg_ex in  re_dict.items():
        matched = reg_ex.search(lookup)
        if matched:
            value = key.upper()
            break
    return value

所以基本上,我们循环遍历字典值(它们是正则表达式),当我们找到匹配项时,我们获取键并成为新的标准化值。

但是由于我们在字典中循环,我们正在失去与字典类型哈希表相关的速度。但是我们要如何克服呢?我可以简单地交换这些字典中的键值对吗?但是,我的 reg_lookup 函数需要如何更改,它会更快吗?

另一个示例字典:

osVerRE = collections.OrderedDict([
    ('WINDOWS', collections.OrderedDict([
        ('WINDOWS SERVER 2000 SERIES', re.compile('^(?=.*WINDOWS)(?=.*SERVER)(?=.*2000).*$', re.MULTILINE+re.IGNORECASE)),
        ('WINDOWS SERVER 2003 ENTERPRISE', re.compile('^(?=.*WINDOWS)(?=.*SERVER|.* SR )(?=.*2003)(?=.*ENTERPRISE|.* Ent).*$', re.MULTILINE+re.IGNORECASE)),
        ('WINDOWS SERVER 2003 STANDARD', re.compile('^(?=.*WINDOWS)(?=.*SERVER)(?=.*2003)(?=.*STANDARD|.*STD).*$', re.MULTILINE+re.IGNORECASE)),
        ('WINDOWS SERVER 2003 SERIES', re.compile('^(?=.*WINDOWS)(?=.*SERVER)(?=.*2003).*|(?=.*WIN2003).*|(?=.*Windows)(?=.*SERVER)(?=.*2k3).*$', re.MULTILINE+re.IGNORECASE)),
        ('WINDOWS SERVER 2008 ENTERPRISE', re.compile('^(?=.*WINDOWS)(?=.*SERVER)(?=.*2008)(?=.*ENTERPRISE|.* ENT).*|(?=.*WINDOWS)(?=.*SERVER)(?=.*2K8)(?=.*ENTERPRISE|.* ENT).*$', re.MULTILINE+re.IGNORECASE)),
        ('WINDOWS SERVER 2008 STANDARD', re.compile('^(?=.*WINDOWS)(?=.*SERVER)(?=.*2008)(?=.*STANDARD|.*STD).*|(?=.*WINDOWS)(?=.*SERVER)(?=.*2K8)(?=.*STANDARD|.*STD).*$', re.MULTILINE+re.IGNORECASE)),
        ('WINDOWS SERVER 2008 DATACENTER', re.compile('^(?=.*WINDOWS)(?=.*SERVER)(?=.*2008)(?=.*DATACENTER).*$', re.MULTILINE+re.IGNORECASE)),
        ('WINDOWS SERVER 2008 SERIES', re.compile('^(?=.*WINDOWS)(?=.*SERVER)(?=.*2008).*|(?=.*WINDOWS)(?=.*SERVER)(?=.*2K8).*|(?=.*WIN2008).*|(?=.*WINDOWS 2K8 R2).*$', re.MULTILINE+re.IGNORECASE)),
        ('WINDOWS SERVER 2012 DATACENTER', re.compile('^(?=.*WINDOWS)(?=.*SERVER)(?=.*2012)(?=.*DATACENTER).*$', re.MULTILINE+re.IGNORECASE)),
        ('WINDOWS SERVER 2012 STANDARD', re.compile('^(?=.*WINDOWS)(?=.*SERVER)(?=.*2012)(?=.*STANDARD).*$', re.MULTILINE+re.IGNORECASE)),
        ('WINDOWS SERVER 2012 SERIES', re.compile('^(?=.*WINDOWS)(?=.*SERVER)(?=.*2012).*|(?=.*WINDOWS)(?=.*2012 R2).*$', re.MULTILINE+re.IGNORECASE)),
        ('WINDOWS NT 4.0', re.compile('^(?=.*WINDOWS)(?=.*NT)(?=.*4\.0).*$', re.MULTILINE+re.IGNORECASE)),
        ('WINDOWS XP PROFESSIONAL', re.compile('^(?=.*WINDOWS)(?=.*XP)(?=.*PROFESSIONAL).*$', re.MULTILINE+re.IGNORECASE)),
        ('WINDOWS 10 PROFESSIONAL', re.compile('^(?=.*WINDOWS)(?=.*10)(?=.*PRO).*$', re.MULTILINE+re.IGNORECASE)),
        ('WINDOWS 7 ENTERPRISE', re.compile('^(?=.*WINDOWS)(?=.*7)(?=.*ENTERPRISE).*$', re.MULTILINE+re.IGNORECASE)),
        ('WINDOWS 7 PROFESSIONAL', re.compile('^(?=.*WINDOWS)(?=.*7)(?=.*PROFESSIONAL).*$', re.MULTILINE+re.IGNORECASE)),
        ('WINDOWS 7 ULTIMATE', re.compile('^(?=.*WINDOWS)(?=.*7)(?=.*ULTIMATE).*$', re.MULTILINE+re.IGNORECASE)),
        ('WINDOWS SERVER LINE', re.compile('^(?=.*WINDOWS)(?=.*SERVER).*$', re.MULTILINE+re.IGNORECASE)),
        ('WINDOWS XP LINE', re.compile('^(?=.*WINDOWS)(?=.*XP).*$', re.MULTILINE+re.IGNORECASE)),
        ('WINDOWS 7 LINE', re.compile('^(?=.*WINDOWS)(?=.*7).*$', re.MULTILINE+re.IGNORECASE)),
        ('WINDOWS 8 LINE', re.compile('^(?=.*WINDOWS)(?=.*8).*$', re.MULTILINE+re.IGNORECASE)),
        ('WINDOWS 10 LINE', re.compile('^(?=.*WINDOWS)(?=.*10).*$', re.MULTILINE+re.IGNORECASE))
    ])),
.
.
.

【问题讨论】:

  • 好吧,如果你必须循环,你就必须循环......为什么首先要使用字典?
  • 如果您仍然需要检查每个表达式是否匹配,有什么区别?
  • @juanpa.arrivillaga:你不必循环,因为词法分析器也可以完成这些任务,并且在要解析的内容长度上工作,而不是潜在标记的数量。
  • 好吧,如果你要循环,循环遍历一个元组列表。
  • 为什么所有的正则表达式都使用^.*.*$?这些是多余的,re.MULTILINE 标志也是如此。您只需要查找是否存在特定单词(或一组单词中的一个)。

标签: python regex dictionary hashtable


【解决方案1】:

与其以期望的结果作为键,将每个可能的结果的表达式作为值来循环遍历字典,为什么不进行一次正则表达式搜索(因为您的表达式非常基本),然后在字典中查找结果?

def reg_lookup(lookup, expression=re.compile('windows|WIN2008|WIN2003|unix|linux|ubuntu|red hat|redhat|RHEL|CentOS|CENT OS|Debian|SLES|SUSE|freebsd|free bsd|AIX|Solaris|SunOS|mac os x|VOS|Stratus|OS400|AS400|IOS|NETWARE|OES|Open Enterprise Server|VMS|ESX|NONSTOP|Tandem|NON STOP|H06.20|QNX', re.MULTILINE|re.IGNORECASE), dct={'windows':'WINDOWS', 'WIN2008':'WINDOWS', 'WIN2003':'WINDOWS', 'unix':'LINUX/UNIX', 'linux':'LINUX/UNIX'}):
    result = expression.search(lookup)
    if result:
        return dct[result.group()]
    else:
        return 'INDETERMINATE'

请注意,H06.20 匹配 . 的任何字符。如果你想要一个文字点,请使用H06\.20

【讨论】:

  • 您还应该按位或标志参数 - re.MULTILINE | re.IGNORECASE 而不是 re.MULTILINE+re.IGNORECASE
  • 很好的答案,唯一的问题是,这种方法完全不需要正则表达式。即对于所有 250k 计算机资产,我可以创建一个字典,其中每个观察到的变化作为键,标准化值作为值。这将充分利用哈希表的速度,但我担心它本质上不会那么灵活和动态。谢谢你。更正。
  • 你能解释一下为什么按位或更好吗?
  • @user3656612 - 它必须有多“灵活和动态”?您是否打算使用真正利用正则表达式功能的表达式,或者它们总是预先确定的字符串?如果完整的 lookup 始终是几个预定义字符串之一,那么它首先应该只是 dct.get(lookup, 'INDETERMINATE'),对于 O(1) 运行时。
  • 你可能是对的,为了加快速度,不要使用正则表达式,只需查找值。但是你必须考虑数据的性质;即字典会很大。例如只取WINDOWS 行,这将匹配所有包含windows、win2008 或win2003 字样的内容。但是,如果我只是将实际未标准化的值设为键,例如,它将在 Windows 2003 sp1 上失败......但是......现在我看到这就是您的正则表达式解决方案发挥作用的地方。嗯...
【解决方案2】:

这是我正在使用的解决方案,至少目前是这样。它利用哈希表的速度来查找先前使用字典查找匹配的项目,并在查找新项目期间形成更快的字典。

我的速度提高了 50% 到 75%,具体取决于标准化的数据源。它有点令人费解,但不是一个糟糕的解决方案......似乎提供了两全其美的解决方案。想法?

fast_lookup = {"mfr":{}, "model":{}, "dev_type":{}, "os_type":{}, "os_ver":{}, "country":{}, "state":{}, }

def reg_lookup(lookup, re_dict, dict_name):
    global fast_lookup
    try:
        #First try the faster dynamically generated dictionary
        return fast_lookup[dict_name][lookup]
    except:
        #Otherwise, use the lookup dictionaries
        for key, reg_ex in  re_dict.items():
            matched = reg_ex.search(lookup)
            if matched:
                #Should a match be found, add it to the faster dictionary
                fast_lookup[dict_name][lookup] = key.upper()
                return key.upper()
        return "INDETERMINATE"

【讨论】:

    【解决方案3】:

    这些是一些非常可怕的正则表达式。看起来它们都归结为一些固定的字符串,一些带有选项和必需的组合,没有一个带有顺序。我的第一反应是将它们包装在(?P<name>...) 部分中并将它们加入一个巨大的正则表达式中,之后您可以请求 groupdict;鉴于内部没有捕获组(更不用说命名的组),您甚至可以使用 lastgroup (但您希望颠倒测试的顺序,并且它不会捷径)。但这会使代码更加难看。我更喜欢一种方法,其中子字符串不包含在^.*(?=.*...).*$ 中,而是简单列出;噪音太大了。

    这是一种粗略的转换方法,用于提取字符串本身并生成一些可匹配的集合:

    import re, itertools
    
    l=[('WINDOWS SERVER 2000 SERIES', re.compile('^(?=.*WINDOWS)(?=.*SERVER)(?=.*2000).*$', re.MULTILINE+re.IGNORECASE)),
            ('WINDOWS SERVER 2003 ENTERPRISE', re.compile('^(?=.*WINDOWS)(?=.*SERVER|.* SR )(?=.*2003)(?=.*ENTERPRISE|.* Ent).*$', re.MULTILINE+re.IGNORECASE)),
            ('WINDOWS SERVER 2003 STANDARD', re.compile('^(?=.*WINDOWS)(?=.*SERVER)(?=.*2003)(?=.*STANDARD|.*STD).*$', re.MULTILINE+re.IGNORECASE)),
            ('WINDOWS SERVER 2003 SERIES', re.compile('^(?=.*WINDOWS)(?=.*SERVER)(?=.*2003).*|(?=.*WIN2003).*|(?=.*Windows)(?=.*SERVER)(?=.*2k3).*$', re.MULTILINE+re.IGNORECASE)),]
    
    def re2wordsets(e):
        # Extracts the words, and converts alternatives into multiple sets.
        groups=map(str.lower,re.findall(r'\(\?=\.\*([^)]*)\)', e.pattern))
        groups=[g.split('|.*') for g in groups]
        for words in itertools.product(*groups):
             yield set(words)
    
    # Convert to set form
    l2 = [(k,list(re2wordsets(e))) for (k,e) in l]
    # Collect all words that are used
    allwords = reduce(set.union, itertools.chain(*(i[1] for i in l2)))
    # Build a search regex to find any of them
    allwordsre = re.compile('|'.join(allwords), re.IGNORECASE | re.MULTILINE)
    
    # Use that regex once to find all relevant words in some string
    foundwords = set(map(str.lower, allwordsre.findall(somestring)))
    # Then see if we find a matching set
    # Note: this could be simplified if we flatten the list of sets
    # e.g. [(k,v1), (k,v2)] instead of [(k,[v1,v2])]
    for k,vs in l2:
        for v in vs:
            if v.issubset(foundwords):
                print k
                break
        else:     # none of our sets matched, check next entry
            continue
        break
    

    顺便说一句,在 CPython 2.7.10 中,名义问题的答案是肯定的,re 模式对象 (SRE_Pattern) 是可散列的并且可以用作键,尽管这可能取决于实现,因为它没有在文档。虽然我不确定这是否真的适用于这个问题。

    【讨论】:

    • 这个问题的主要原因是为了加快速度。即现在的方式,我必须遍历字典值并进行正则表达式一一比较。 150,000 条记录大约需要 50 秒,我想要更快的东西。所以我正在考虑反转键和值......但真的不知道这会如何工作。
    猜你喜欢
    • 2012-03-18
    • 2013-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-25
    • 1970-01-01
    • 2011-03-08
    相关资源
    最近更新 更多