如何规避Python的os.path.commonprefix的谬误?

时间:2014-02-01 14:00:24

标签: python path prefix

我的问题是找到给定文件集的公共路径前缀。

从字面上看,我期待“os.path.commonprefix”可以做到这一点。不幸的是,commonprefix位于path的事实相当具有误导性,因为它实际上会搜索字符串前缀。

对我来说,问题是如何才能真正解决路径?在this (fairly high rated) answer中简要提到了这个问题,但只是作为附注并且提议的解决方案(对commonprefix的输入附加斜线)imho存在问题,因为它将失败例如:

os.path.commonprefix(['/usr/var1/log/', '/usr/var2/log/'])
# returns /usr/var but it should be /usr

为了防止其他人陷入同一陷阱,可能值得在一个单独的问题中讨论这个问题:是否有一个简单/可移植的解决方案,不依赖于对文件系统的讨厌检查(即,访问commonprefix的结果并检查它是否是一个目录,如果没有,则返回结果的os.path.dirname

5 个答案:

答案 0 :(得分:11)

前一段时间我碰到了os.path.commonprefix是一个字符串前缀,而不是预期的路径前缀。所以我写了以下内容:

def commonprefix(l):
    # this unlike the os.path.commonprefix version
    # always returns path prefixes as it compares
    # path component wise
    cp = []
    ls = [p.split('/') for p in l]
    ml = min( len(p) for p in ls )

    for i in range(ml):

        s = set( p[i] for p in ls )         
        if len(s) != 1:
            break

        cp.append(s.pop())

    return '/'.join(cp)

可以通过将'/'替换为os.path.sep来使其更具可移植性。

答案 1 :(得分:10)

似乎在最近的Python版本中已经纠正了这个问题。版本3.5中的新功能是函数os.path.commonpath(),它返回公共路径而不是公共字符串前缀。

答案 2 :(得分:6)

假设您需要公共目录路径,一种方法是:

  1. 仅使用目录路径作为输入。如果您的输入值是文件名,请调用os.path.dirname(filename)以获取其目录路径。
  2. “规范化”所有路径,使它们相对于同一个路径,并且不包括双分隔符。最简单的方法是调用os.path.abspath( )来获取相对于根的路径。 (您可能还想使用os.path.realpath( )删除符号链接。)
  3. 在所有规范化目录路径的末尾添加最终分隔符(可以os.path.sepos.sep移植)。
  4. os.path.dirname( )
  5. 的结果致电os.path.commonprefix( )

    在代码中(不删除符号链接):

    def common_path(directories):
        norm_paths = [os.path.abspath(p) + os.path.sep for p in directories]
        return os.path.dirname(os.path.commonprefix(norm_paths))
    
    def common_path_of_filenames(filenames):
        return common_path([os.path.dirname(f) for f in filenames])
    

答案 3 :(得分:2)

一种强大的方法是将路径拆分为单个组件,然后找到组件列表中最长的公共前缀。

这是一个跨平台的实现,可以很容易地推广到两个以上的路径:

import os.path
import itertools

def components(path):
    '''
    Returns the individual components of the given file path
    string (for the local operating system).

    The returned components, when joined with os.path.join(), point to
    the same location as the original path.
    '''
    components = []
    # The loop guarantees that the returned components can be
    # os.path.joined with the path separator and point to the same
    # location:    
    while True:
        (new_path, tail) = os.path.split(path)  # Works on any platform
        components.append(tail)        
        if new_path == path:  # Root (including drive, on Windows) reached
            break
        path = new_path
    components.append(new_path)

    components.reverse()  # First component first 
    return components

def longest_prefix(iter0, iter1):
    '''
    Returns the longest common prefix of the given two iterables.
    '''
    longest_prefix = []
    for (elmt0, elmt1) in itertools.izip(iter0, iter1):
        if elmt0 != elmt1:
            break
        longest_prefix.append(elmt0)
    return longest_prefix

def common_prefix_path(path0, path1):
    return os.path.join(*longest_prefix(components(path0), components(path1)))

# For Unix:
assert common_prefix_path('/', '/usr') == '/'
assert common_prefix_path('/usr/var1/log/', '/usr/var2/log/') == '/usr'
assert common_prefix_path('/usr/var/log1/', '/usr/var/log2/') == '/usr/var'
assert common_prefix_path('/usr/var/log', '/usr/var/log2') == '/usr/var'
assert common_prefix_path('/usr/var/log', '/usr/var/log') == '/usr/var/log'
# Only for Windows:
# assert common_prefix_path(r'C:\Programs\Me', r'C:\Programs') == r'C:\Programs'

答案 4 :(得分:2)

我已经创建了一个小的python包commonpath来查找列表中的常用路径。附带一些不错的选择。

https://github.com/faph/Common-Path