2007-07-26

 

Python Cookbook 2.22 在两个路径间计算相对路径

需求:

你需要知道从一个目录到另一个目录的相对路径,比如,想创建 一个符号连接或者在URL中引用.

讨论:

最简单的办法就是将路径分割成目录列表,然后依次处理.我们可以写一个助手方法来实现:

import os, itertools
def all_equal(elements):
    ''' return True if all the elements are equal, otherwise False. '''
    first_element = elements[0]
    for other_element in elements[1:]:
        if other_element != first_element: return False
    return True
def common_prefix(*sequences):
    ''' return a list of common elements at the start of all sequences,
        then a list of lists that are the unique tails of each sequence. '''
    # if there are no sequences at all, we're done
    if not sequences: return [  ], [  ]
    # loop in parallel on the sequences
    common = [  ]
    for elements in itertools.izip(*sequences):
        # unless all elements are equal, bail out of the loop
        if not all_equal(elements): break
        # got one more common element, append it and keep looping
        common.append(elements[0])
    # return the common prefix and unique tails
    return common, [ sequence[len(common):] for sequence in sequences ]
def relpath(p1, p2, sep=os.path.sep, pardir=os.path.pardir ):
    ''' return a relative path from p1 equivalent to path p2.
        In particular: the empty string, if p1 == p2;
                       p2, if p1 and p2 have no common prefix.
    '''
    common, (u1, u2) = common_prefix(p1.split(sep), p2.split(sep))
    if not common:
        return p2      # leave path absolute if nothing at all in common
    return sep.join( [pardir]*len(u1) + u2 )
def test(p1, p2, sep= os.path.sep):
    ''' call function relpath and display arguments and results. '''
    print "from", p1, "to", p2, " -> ", relpath(p1, p2, sep)
if _ _name_ _ == '_ _main_ _':
    test('/a/b/c/d', '/a/b/c1/d1', '/')
    test('/a/b/c/d', '/a/b/c/d', '/')
    test('c:/x/y/z', 'd:/x/y/z', '/')

在本节的代码中,使用最多的是简单却又最常使用的common_prefix函数,给定该函数一个长度为N的序列,它返回序列中元素的共同前缀, 并分别返回由不同部分组成的列表.为了计算两个路径间的相对路径,我们可以忽略它们中相同的部分.我们只关心向上的层数就可以了(一般来说,用os.path,pardir表示,在Unix系统中用../来表示,我们需要用它来表示从开始路径向下的层数),然后添加不同的后缀就可以了.所以,函数relpath将路径分割成目录,然后调用common_prefix,接着计算共同的层数.
common_prefix代码中间的循环语句for elements in itertools.izip(*sequences),因为izip语句只有当它遇到最短的元素的结尾时才会中止,所以我们的程序中需要自己判断获得的字串是不是在所有元素中都存在,并且将所有的公共元素都添加到common中,当循环结束的时候,我们通过common来对各个元素进行切片处理,以获得不同部分的列表.
函数all_euqal可以用另外一种简单的方式来实现,也是很有趣的:

def all_equal(elements):
    return len(dict.fromkeys(elements)) == 1

或者,用更简单的方法,在Python2.4后有效:

def all_equal(elements):
    return len(set(elements)) == 1

因为所有元素相等等价于所有元素组成的集合的大小为1,是使用dict.fromkeys的版本中,我们使用dict来模拟set的功能,所以它可以在Python2.3和2.4中工作 ,而set版本只能在2.4中工作.

相关说明:

class izip(__builtin__.object)
 |  izip(iter1 [,iter2 [...]]) --> izip object
 |
 |  Return a izip object whose .next() method returns a tuple where
 |  the i-th element comes from the i-th iterable argument.  The .next()
 |  method continues until the shortest iterable in the argument sequence
 |  is exhausted and then it raises StopIteration.  Works like the zip()
 |  function but consumes less memory by returning an iterator instead of
 |  a list.

Comments: 发表评论



<< Home

This page is powered by Blogger. Isn't yours?