【问题标题】:regex expression for extracting a base file name from a path用于从路径中提取基本文件名的正则表达式
【发布时间】:2014-09-03 13:27:50
【问题描述】:

我想从某些文件路径中获取文件的字母部分。

files = ['data/Conversion/201406/MM_CLD_Conversion_Advertiser_96337_Daily_140606.zip', 
         'data/Match/201406/MM_CLD_Match_Advertiser_111423_Daily_140608.csv.zip', 
         'data/AQlog/201406/orx140605.csv.zip',
         'data/AQlog/201406/orx140605.csv.zip/']

目前我这样做:

  1. 带尾斜线
  2. os.path.split()[1] 获取文件名
  3. 两个 os.path.splitext() 删除可能的 2 个文件扩展名
  4. 输数字
  5. 省略下划线

代码:

for f in files:
    a = os.path.splitext(os.path.splitext(os.path.split(f.rstrip('/\\'))[1])[0])[0]
    b = re.sub('\d+', '', a).replace('_','')

结果:

'MMCLDConversionAdvertiserDaily'
'MMCLDMatchAdvertiserDaily'
'orx'
'orx'

有没有更快或更pythonic的方式,使用编译的正则表达式函数?还是尝试使用库os.path() 非常合理?我也不必这样做超过 100 次,所以这不是速度问题,这只是为了清楚起见。

【问题讨论】:

  • 当你在下一个句子中说“我也不必这样做超过 100 次,所以这不是速度问题”时,为什么要问“有没有更快的……方法”?如果速度不是问题,为什么要要求速度?
  • 另外,您知道如何查找文档吗?如果是这样,为什么使用 os.path.split()[1] 而不是 os.path.basename()
  • 嗨@abarnert!我只是对速度感到好奇,因为我的印象是编译的正则表达式函数非常快。是的,错过了os.path.basename(),因为我之前玩过 os.path 并且这次没有仔细查看文档。
  • 我将在我的答案中添加一些关于已编译正则表达式的内容......

标签: python regex


【解决方案1】:

您可以使用os.path 中的适当函数来简化此操作。

首先,如果您调用 normpath,您不再需要担心这两种路径分隔符,只需 os.sep(请注意,如果您尝试这样做,不好,例如,在 Linux 上处理 Windows 路径……但如果您尝试在任何给定平台上处理本机路径,这正是您想要的)。它还会删除任何尾随斜杠。

接下来,如果您调用basename 而不是split,则不再需要输入那些尾随的[1]s。

不幸的是,没有与 basenamesplit 对等的 splitext... 但您可以轻松编写一个,这将使您的代码更具可读性,就像使用 basename 一样。

至于其余部分……正则表达式是去除任何数字的明显方法(尽管您确实不需要那里的+)。而且,由于您已经有了一个正则表达式,因此将_ 扔在那里可能会更简单,而不是单独进行。

所以:

def stripext(p):
    return os.path.splitext(p)[0]

for f in files:
    a = stripext(stripext(os.path.basename(os.path.normpath(f))))
    b = re.sub(r'[\d_]', '', a)

当然,如果将 if 包装成一个函数,整个内容可能更具可读性:

def process_path(p):
    a = stripext(stripext(os.path.basename(os.path.normpath(f))))
    return re.sub(r'[\d_]', '', a)

for f in files:
    b = process_path(f)

特别是因为您现在可以将循环转换为列表理解或生成器表达式或map 调用:

processed_files = map(process_path, files)

我只是对速度感到好奇,因为我的印象是编译的正则表达式函数非常快。

嗯,是的,一般来说。不过,未编译的字符串模式也很快。

当您使用字符串模式而不是编译的正则表达式对象时,会发生以下情况:

  • re 模块在已编译正则表达式的缓存中查找模式。
  • 如果未找到,则编译字符串并将结果添加到缓存中。

因此,假设您没有在应用程序中使用几十个正则表达式,无论哪种方式,您的模式都会只编译一次,然后作为编译表达式重复运行。使用未编译表达式的唯一额外成本是在缓存字典中查找它,这非常便宜 - 特别是当它是字符串文字时,因此每次都保证是完全相同的字符串对象,因此它的哈希将被缓存为好吧,所以在第一次之后,dict 查找变成了 mod 和数组查找。

对于大多数应用程序,您可以假设re 缓存足够好,因此决定是否预编译正则表达式的主要原因是可读性。例如,如果你有一个函数,它运行大量复杂的正则表达式,其目的难以理解,给每个函数命名肯定会有所帮助,所以你可以写for r in (r_phone_numbers, r_addresses, r_names): …,在在这种情况下,不编译它们几乎是愚蠢的。

【讨论】:

    【解决方案2】:

    不使用正则表达式:

    import os
    import string
    trans = string.maketrans('_', ' ')
    def get_filename(path):
        # If you need to keep the directory, use os.path.split
        filename = os.path.basename(path.rstrip('/'))
        try:
            # If the extension starts at the last period, use
            # os.path.splitext
            # If the extension starts at the 2nd to last period,
            # use os.path.splitext twice
            # Continuing this pattern (since it sounds like you
            # don't know how many extensions a filename may have,
            # it may be safer to assume the file extension starts
            # at the first period. In which case, use
            # filename.split('.', 1).
            filename_without_ext, extension = filename.split('.', 1)
        except ValueError:
            filename_without_ext = filename
            extension = ''
        filename_cleaned = filename_without_ext.translate(trans, string.digits)
        return filename_cleaned
    
    >>> path = 'data/Match/201406/MM_CLD_Match_Advertiser_111423_Daily_140608.csv.zip/'
    >>> get_filename(path)
    'MM CLD Match Advertiser  Daily '
    

    采用任何更具可读性的方法。如果问题不需要,我通常会避免使用正则表达式。在这种情况下,常规的字符串操作可以做任何你想做的事情。

    如果您想删除多余的空格(如结果中所示),请使用filename.replace(' ', '')。如果您可能有其他类型的空格,可以通过''.join(filename.split())删除它

    注意:如果您使用的是 Python 3,请将 trans=string.maketrans('_', ' ') 替换为 trans=str.maketrans('_', ' ', string.digits),并将 filename_without_ext.translate(trans, string.digits) 替换为 filename_without_ext.translate(trans)This change 是改进 unicode 语言支持的一部分。查看更多:How come string.maketrans does not work in Python 3.1?

    这是 Python 3 代码:

    import os
    import string
    trans = string.maketrans('_', ' ', string,digits)
    def get_filename(path):
        filename = os.path.basename(path.rstrip('/'))
        filename_without_ext = filename.split('.', 1)[0]
        filename_cleaned = filename_without_ext.translate(trans)
        return filename_cleaned
    

    【讨论】:

    • 你没有去掉 OP 要求和正在做的数字——这是一个完美的例子,说明正则表达式有好处,而常规字符串操作不是……
    • 已使用字符串翻译的 deletechars 参数修复。
    • 好的,它现在可以工作了——尽管现在它的宽度也超过了 80 个字符,并且在 Python 3.x 中不再工作了。我不确定是否值得如此程度地避免正则表达式(即使在不必要的情况下使用正则表达式也更值得去荒谬的程度,就像很多人那样......),但至少值得展示如何这样做,所以绝对+1。 (但最好是符合 PEP8 并且没有水平滚动条,并解释您添加的 2.x-3.x 差异。)
    • @abarnert 感谢您帮助清理此答案。
    • 你的最后一段不是你想要的 Python 3。你的文件名是str,而不是bytes,所以你不能使用bytes.maketrans。你想要的是str.maketrans。但是,更重要的是,str.translate 不再接受第二个参数;您想将要删除的字符作为第三个参数传递给str.maketrans。 (另外,在 2.x 和 3.x 中,您不妨使用string.digits。)
    猜你喜欢
    • 2012-03-10
    • 1970-01-01
    • 2021-06-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多