从路径中提取基本文件名的正则表达式
我想从一些文件路径中提取出字母部分。
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/']
目前我这样做:
- 去掉路径末尾的斜杠
- 用
os.path.split()[1]
获取文件名 - 用两次
os.path.splitext()
去掉可能的两个文件扩展名 - 去掉数字
- 去掉下划线
代码:
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'
有没有更快或者更符合Python风格的方法,可以使用编译好的正则表达式函数?或者使用 os.path()
这个库的做法是否很合理?我也不需要做超过100次,所以这不是速度问题,主要是为了清晰。
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)
。这个变化是为了改善对 Unicode 语言的支持。想了解更多,可以查看:为什么 string.maketrans 在 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
你可以通过使用 os.path
中的合适函数来简化这个过程。
首先,如果你调用 normpath
,那么你就不需要担心两种路径分隔符了,只需要关注 os.sep
(注意,如果你是在 Linux 上处理 Windows 路径,这可能会有问题……但如果你是在处理某个平台的本地路径,这正是你想要的)。而且它还会去掉路径末尾的斜杠。
接下来,如果你使用 basename
而不是 split
,那么你就不需要再加那些末尾的 [1]
了。
可惜的是,splitext
没有类似于 basename
和 split
的替代方法……但你可以很容易地写一个,这样可以让你的代码在可读性上和使用 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)
当然,如果你把整个过程封装成一个函数,可能会更易读:
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
模块会在编译好的正则表达式缓存中查找这个模式。- 如果没有找到,就会编译这个字符串,并把结果添加到缓存中。
所以,假设你在应用中没有使用很多几十个正则表达式,无论哪种方式,你的模式都会被编译一次,并作为编译后的表达式重复运行。使用未编译表达式的唯一额外成本是查找缓存字典,这个成本非常低——尤其是当它是一个字符串字面量时,它每次都是同一个字符串对象,所以它的哈希值也会被缓存,因此在第一次查找后,字典查找就变成了简单的 mod
和数组查找。
对于大多数应用,你可以假设 re
缓存已经足够好,所以决定是否预编译正则表达式的主要原因是可读性。例如,当你有一个函数运行一堆复杂的正则表达式,而这些表达式的目的很难理解时,给每个表达式起个名字会很有帮助,这样你可以写 for r in (r_phone_numbers, r_addresses, r_names): …
,在这种情况下,不编译它们几乎显得有些傻。