2014-01-16 70 views
0

文件结构如下所示:扫描多序列文件

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文件,也可以是其他格式。

+0

我不清楚你在问什么。请提供更多细节。 – CrazyCasta

回答

2

使用os.walk走树。由于这不会给你文件大小,所以你需要在每个文件上调用os.stat

接下来,显然你想先按扩展名进行分组,然后按基本文件名(如果两个文件名之间的唯一区别是某些数字部分关闭1,则两个文件名一起进行分组),但按文件名对文件名进行排序。一般来说,分组最简单的方法是对它们进行排序,然后通过邻接功能进行分组,然后您可以随后对其进行排序。

我不知道你的实际分组的关键应该是,因为我想不出任何明智的,将自0001-2000分离2004年,但不是从2501同样分开,我不可以肯定的是,尽管存在差距,这个规则会给你带来什么。所以我会把这些部分留给你。

所以:

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检查每个新值对组中的最后第一。如果是后者,这个keyfunc将不起作用。您可以尝试编写一个有状态的比较器,但有一个更简单的解决方案:groupby为您提供了等效的Python源代码,并且它不那么复杂,因此您可以将其复制并粘贴到代码中并将其更改为记住组中最近的价值。最后,如果整个迭代器和groupby等处理都听起来像是希腊语,那么不要试图在代码运行之前对它进行轰炸。 Generator Tricks for System Programmers会教你希腊语,像这样的一类问题将会在你的余生中更容易。 (好吧,直到你不得不在没有发电机另一种语言写的...)


*我相当确保你不需要int(number, 10),因为Python 2.7和3.x将不会将int('0123')解释为八进制...但是由于我必须查明它的可信度,因此明确表示它似乎是一个可读性的好主意。

+0

嗨,abarnert:我非常感谢你的回复,这非常有帮助,差距实际上是我的文章的一个错字,对不起,我纠正了它。你的代码,虽然我不完全理解它是如何工作的,因为我是一个新的Python学习者,我需要一些时间来挖掘你的答案,我学到了很多东西。非常感谢你。 –

+0

abarnert,你会介意帮助我完成keycmp(),我想通过它们的连续性或邻接性来对它们进行分组,我不知道如何判断两个字符串是否与字符串中的数字相邻。我应该将字符串中的数字分开,然后比较它们吗?有没有更好的办法?再次感谢您的帮助。 –

+0

@liaozd:是的,你可能想分开字符串中的数字。有预先制作的“自然比较”图书馆,但要解决“......和1相同”的问题,但对于那些图书馆来说,可能与编写自己的比较一样困难(因为您不必像他们一样完全一般)。看到我编辑的答案。 – abarnert

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
所以要根据你的系统,你可能要考虑改变这一点。

+0

我认为你已经解决了这个人类可读格式文件大小的简单部分 - 但不是将相邻文件组合在一起的困难部分。 – abarnert

+0

inspectorG4dget,谢谢,它有帮助。我需要将“相邻”文件组合在一起。每个图像文件都是电影中的帧,并且每个连续图像序列都是一个镜头。 –

+0

检查编辑。它应该可以工作,只要所有的文件都被命名为'nameNNNN.jpg'或'nameNNNN.tiff' – inspectorG4dget

0

@abarnert:从您的博客得出下面的代码:分组到相邻值(链接:http://stupidpythonideas.blogspot.com/2014/01/grouping-into-runs-of-adjacent-values.html)的运行

我尝试python2.6.6在Win7和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]] 
+0

好的,问题是来自docs的纯Python'groupby'将'tgtkey'作为参数传递给'_grouper',而不是'_grouper'使用该属性。这似乎只不过是微优化(局部变量查找比实例属性查找更快),这几乎不会有关系。这意味着我们最终测试一个组的最后一个元素来决定是否完成,所以如果最后一个元素在一个大于2的组中,它最终会重复。让我编辑博客文章;感谢抓住这一点。 – abarnert

+0

但是,你不应该使用hack-up'groupby'。正如博客文章解释的那样,在实现这两种方式之前似乎更简单,但编写一个可以与标准'groupby'配合使用的关键函数变得更简单和更清晰。 (另外,注意:将代码从3.x移植到2.x时,总是将'class foo:'更改为'class foo(object):'或者获得旧式类。)最后,发布一个答案像这是真的滥用SO系统。如果发表评论时有链接到pastebin,或对我的博客文章发表评论,或发表一个新问题或其他任何内容,本来会更好。 – abarnert