扫描许多序列文件

时间:2014-01-16 00:33:57

标签: python

文件结构如下所示:

folder1
    |-----name0000.jpg
    |-----name0000.tif
    |-----name0001.jpg
    |-----name0001.tif
    |-----....   
    |-----....
    |-----name2000.jpg
    |-----name2000.tif
    |-----name2004.tif
    |-----....
    |-----name2845.tif
    |-----other_file.txt
    |-----folder2
                 |-----name0000.jpg
                 |-----name0000.tif
                 |-----name0001.jpg
                 |-----name0001.tif
                 |-----....   
                 |-----....
                 |-----name2000.jpg
                 |-----name2000.tif
                 |-----other_file2.sh

如何将它们组成这样的组?

  ./folder1: name0000-2000.jpg, 340MB
  ./folder1: name0000-2000.tif, 1GB
  ./folder1: name2004-2845.tif, 500MB
  ./folder1: other_file.txt, 1k
  ./folder1/folder2: name0000-2000.jpg, 340MB
  ./folder1/folder2: name0000-2000.tif, 1GB
  ./folder1/folder2: other_file2.sh, 45byte

总文件数可能是数万,我想要速度。不仅有jpg和tif文件,还可以是其他格式。

3 个答案:

答案 0 :(得分:2)

大部分工作是将文件大小变为人类可读的格式。看看这是否适合您

import os

def sizify(fpath):
    bytes = os.stat(fpath).st_size
    suff = 0
    while b//1000:
        b = b//1000
        suff += 1
    return str(b) + ["B", "MB", "GB" "TB"][suff]

def humanReadable(bytes):
    suff = 0
    while b//1000:
        b = b//1000
        suff += 1
    return str(b) + ["B", "MB", "GB" "TB"][suff]    

def getRuns(fnames):
    fnames.sort()
    answer = []
    start = fnames[0]
    for mid,high in zip(fnames, fnames[1:]):
        mid = int(mid.rsplit('.')[0].lstrip('name'))
        high = int(high.rsplit('.')[0].lstrip('name'))
        if high-mid > 1:
            answer.append((start, mid, 
                          sum(os.stat("name%s.jpg" %i).st_size for i in range(start, mid+1)) +
                          sum(os.stat("name%s.tiff" %i).st_size for i in range(start, mid+1))))
            start = high
    answer.append((start, mid, 
                          sum(os.stat("name%s.jpg" %i).st_size for i in range(start, mid+1)) +
                          sum(os.stat("name%s.tiff" %i).st_size for i in range(start, mid+1))))
    return answer

def main():
    for dir, dirs, files in os.walk('folder1'):
        runs = getRuns(files)
        for low,high,size in runs:
            print("%s: name%s-%s, %s" %(dir, low, high, humanReadable(size)))

请注意,这表示1KB = 1000B而不是1KB = 1024B
因此,根据您所使用的系统,您可能需要考虑更改它。

答案 1 :(得分:2)

使用os.walk走树。由于这不会为您提供文件大小,因此您需要在每个文件上调用os.stat

接下来,显然你想首先按扩展名分组,然后按基本文件名分组(如果两个文件名之间的唯一区别是某些数字部分偏离1,则两个文件名一起分组),但是按文件名对组进行排序。通常,对事物进行分组的最简单方法是对它们进行排序,然后通过itertools.groupby函数按邻接进行分组,然后您可以随后对它们进行排序。

我不确定你的实际分组密钥应该是什么,因为我想不出任何将2004年与0001-2000分开的合理内容,但不能将它与2501分开。同样,我不确定尽管存在差距,2004-2845会给你什么规则。所以我会把这些部分留给你。

所以:

def keyfunc(value):
    base, ext, size = value
    # FILL THIS IN

def format_group(bases):
    # FILL THIS IN

def format_size(size):
    # you can use inspectorG4dget's code here

for root, dirs, names in os.walk(path):
    sizes = (os.stat(name).st_size for name in names)
    bases, exts = zip(*map(os.path.splitext, names))
    files = zip(bases, exts, sizes)
    # now sort by ext, and then by base within each ext
    files = sorted(files, key=operator.itemgetter(1, 0))
    results = []
    for key, group in itertools.groupby(files, key=keyfunc):
        bases, exts, sizes = zip(*list(group))
        results.append((format_group(bases), sum(size))
    for base, size in sorted(results):
        print('{}: {}, {}'.format(root, base, format_size(size)))

在某些情况下,没有明显的分组键功能,但有一种明显的方法可以判断两个相邻值是否属于同一组的一部分。如果是这样,请将其写为旧式cmp函数,如下所示:

def keycmp(x, y):
    if x should be in the same group as y:
        return 0
    return -1

然后您可以使用排序HOWTO中描述的相同functools.cmp_to_key函数:

for key, group in itertools.groupby(files, key=cmp_to_key(keycap)):

但是你这样做,可能会发现到目前为止最慢的部分是在每个文件上调用stat。这是一种耻辱,因为os.walk可能已经拥有该统计信息,但它永远不会给你。

要优化此功能,您可以直接转到本机API,尽可能高效地为您提供信息。大多数现代* nix平台(包括OS X和非古老的linux)都有fts,就像在C中实现的加强版os.walk一样,可以选择为您统计所有文件。较旧的* nixes应该至少有nftwftw。 Windows有FindFirstFile,它更像是一个加强版os.listdir - 它为您提供了每个文件的各种信息,包括大小,非常快,但它没有递归到子目录中,所以你必须手动完成。


如果您的比较应该key0000.jpgkey0001.jpg相同,而不是key0000.jpgkey0002.jpgkey0000.jpgkey0001.tif,显然我们需要将每个名字分解成碎片。并且中间的一个需要转换为数字,因此0009和0010`将相邻(因为它们显然不是字符串)。我想你想要的是:*

pattern = re.compile('(.*?)(\d+)(.*)')
def splitname(name):
    prefix, number, suffix = pattern.match(name).groups()
    return prefix, int(number, 10), suffix

因此,例如,key0000.jpg会分解为'key'0000'.jpg'。玩这个功能,确保它正在做你真正想要的。

接下来,我们如何将它用于比较功能?好吧,它几乎是一个正常的词典比较,除了在中间位,如果左边的一个比右边的一个,它算得相等。所以:

def keycmp(a, b):
    abits, bbits = splitname(a), splitname(b)
    if abits[0] < bbits[0]: return -1
    elif abits[0] > bbits[0]: return 1
    if abits[1]+1 < bbits[1]: return -1
    elif abits[1] > bbits[1]: return 1
    if abits[2] < bbits[2]: return -1
    elif abits[2] > bbits[2]: return 1
    else: return 0
keyfunc = functools.cmp_to_key(keycmp)

(我们实际上并不需要从旧式cmp函数返回完整的-1/0/1,只是非零/ 0 /非零...但它同样简单,可能更具可读性做吧。)

再次,在各种文件名对上调用keycmp,以确保它们正在执行您想要的操作。

你可能会想要在这里进行一些错误处理。按照标准,re.match无法匹配,因为您提供了'files.txt',您将获得AttributeError: 'NoneType' has no attribute 'groups'。但你应该能够弄明白。

最后一件事:我不记得groupby是否针对组中的 last 值或 first 检查每个新值。如果是后者,则keyfunc将不起作用。您可以尝试编写一个有状态的比较器,但是有一个更简单的解决方案:groupby为您提供等效的Python源代码,并不是那么复杂,因此您可以复制它并将其粘贴到您的代码中并更改它要记住组中最近的值。

最后,如果整个处理迭代器和groupby等对你来说听起来像希腊语,那么在它工作之前不要试图直接敲击代码。 Generator Tricks for System Programmers会教你希腊语,这样的各种问题对你来说将更容易。 (好吧,直到你被迫用另一种没有发电机的语言写作......)


*我非常确定你不需要int(number, 10),因为Python 2.7和3.x不会将int('0123')解释为八进制...但是因为我必须看起来可以肯定,明确这一点似乎是一个可读性的好主意。

答案 2 :(得分:0)

@abarnert:它从您的博客派生的以下代码:分组为相邻值的运行(链接:http://stupidpythonideas.blogspot.com/2014/01/grouping-into-runs-of-adjacent-values.html

我在Win7中尝试python2.6.6,在CentOS 6.5中尝试python2.6.6,问题是一样的。因为在这个python 2.6中没有itertools.cmp_to_key(),所以我稍微修改了你的代码,希望这个问题不是来自我的修改。

def adjacent_cmp(x, y):
    if x+1 < y: return -1
    elif x > y: return 1
    else: return 0

def cmp_to_key(mycmp):
    'Convert a cmp= function into a key= function'
    class K:
        def __init__(self, obj, *args):
            self.obj = obj
        def __lt__(self, other):
            return mycmp(self.obj, other.obj) < 0
        def __gt__(self, other):
            return mycmp(self.obj, other.obj) > 0
        def __eq__(self, other):
            return mycmp(self.obj, other.obj) == 0
        def __le__(self, other):
            return mycmp(self.obj, other.obj) <= 0
        def __ge__(self, other):
            return mycmp(self.obj, other.obj) >= 0
        def __ne__(self, other):
            return mycmp(self.obj, other.obj) != 0
    return K

class groupby:
    # [k for k, g in groupby('AAAABBBCCDAABBB')] --> A B C D A B
    # [list(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D
    def __init__(self, iterable, key=None):
        if key is None:
            key = lambda x: x
        self.keyfunc = key
        self.it = iter(iterable)
        self.sentinel = self.tgtkey = self.currkey = self.currvalue = object()
    def __iter__(self):
        return self
    def next(self):
        while (self.currkey is self.sentinel
               or self.tgtkey is not self.sentinel
               and self.tgtkey == self.currkey):
            self.currvalue = next(self.it)    # Exit on StopIteration
            self.currkey = self.keyfunc(self.currvalue)
        self.tgtkey = self.currkey
        return (self.currkey, self._grouper(self.tgtkey))
    def _grouper(self, tgtkey):
        while tgtkey is self.sentinel or tgtkey == self.currkey:
            yield self.currvalue
            self.currvalue = next(self.it)    # Exit on StopIteration
            tgtkey, self.currkey = self.currkey, self.keyfunc(self.currvalue)

adjacent_key = cmp_to_key(adjacent_cmp)
a = [0, 1, 2]
print [list(g) for k, g in groupby(a, adjacent_key)]

[[0, 1, 2], [2]]