<p>使用<a href="http://docs.python.org/3/library/os.html#os.walk" rel="nofollow">^{<cd1>}</a>在树上行走。因为这没有给出文件大小,所以需要对每个文件调用<a href="http://docs.python.org/3/library/os.html#os.stat" rel="nofollow">^{<cd2>}</a>。</p>
<p>接下来,显然要先按扩展名分组,然后按基文件名分组(如果两个文件名之间的唯一区别是某个数字部分被1分开,则两个文件名放在一起),但按文件名对组进行排序。一般来说,对事物进行分组的最简单的方法是对它们进行排序,然后通过<a href="http://docs.python.org/3/library/itertools.html#itertools.groupby" rel="nofollow">^{<cd3>}</a>函数按邻接进行分组,之后您总是可以对它们进行排序。</p>
<p>我不知道你实际的分组键应该是什么,因为我想不出任何合理的方法来区分2004和0001-2000,但不能把它和2501分开。同样,我也不确定哪条规则会给你2004-2845,尽管有差距。所以我把这些部分留给你。</p>
<p>所以:</p>
<pre><code>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)))
</code></pre>
<hr/>
<p>在某些情况下,没有明显的分组键函数,但有一种明显的方法可以判断两个相邻值是否算作同一组的一部分。如果是这样,请将其写成一个老式的<code>cmp</code>函数,如下所示:</p>
^{pr2}$
<p>然后您可以使用排序方法中描述的相同的<a href="http://docs.python.org/3/howto/sorting.html#the-old-way-using-the-cmp-parameter" rel="nofollow">^{<cd5>}</a>函数:</p>
<pre><code>for key, group in itertools.groupby(files, key=cmp_to_key(keycap)):
</code></pre>
<hr/>
<p>不管怎样做,到目前为止最慢的部分可能是对每个文件调用<code>stat</code>。这很可惜,因为<code>os.walk</code>可能已经有了统计信息,但它永远不会给你。</p>
<p>为了优化这一点,您可以直接使用本机api,以尽可能高效地为您提供信息。大多数现代的*nix平台(包括OS X和非古代linux)都有<a href="http://man7.org/linux/man-pages/man3/fts.3.html" rel="nofollow">^{<cd8>}</a>,这类似于用C实现的增强的{<cd1>},它可以选择性地为您统计所有文件。旧的*nixe应该至少有<code>nftw</code>或{<cd11>}。Windows有<a href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa364418%28v=vs.85%29.aspx" rel="nofollow">^{<cd12>}</a>,这更像是一个增强的<code>os.listdir</code>-它可以很快地提供每个文件的所有类型的信息,包括大小,但是它不会递归到子目录中,所以您必须手动执行。</p>
<hr/>
<p>如果您的比较应该使<code>key0000.jpg</code>和<code>key0001.jpg</code>相同,但不是{<cd14>}和{<cd17>}或{<cd14>}和{<cd19>},那么显然我们需要将每个名称分解为多个部分。中间的一个需要转换成一个数字,这样<code>0009</code>和0010`将是相邻的(因为它们显然不是字符串)。我想你想要的是这个:*</p>
^{4}$
<p>因此,例如,<code>key0000.jpg</code>将分解为<code>'key'</code>,<code>0000</code>,和{<cd24>}。使用这个函数,并确保它做你真正想要的。</p>
<p>下一步,我们如何使用它作为比较函数?好吧,这几乎是一个正常的词典比较,除了在中间位,如果左位比右位少1,它就等于。所以:</p>
<pre><code>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)
</code></pre>
<p>(我们实际上不需要从旧样式的<code>cmp</code>函数返回完整的-1/0/1,只需要非0/0/nonzero……但它同样容易,而且可能更易读。)</p>
<p>同样,对不同的文件名对调用<code>keycmp</code>,以确保它们按您的要求运行。</p>
<p>你可能需要一些错误处理。作为标准,<code>re.match</code>不能匹配,因为你给了它,比如说,<code>'files.txt'</code>,你会得到一个<code>AttributeError: 'NoneType' has no attribute 'groups'</code>。但你应该能弄明白。</p>
<p>最后一件事:我不记得<code>groupby</code>是否对照组中的<em>last</em>值检查每个新值,还是<em>第一个</em>。如果是后者,这个<code>keyfunc</code>就不起作用了。您可以尝试编写一个有状态的比较器,但是有一个更简单的解决方案:<a href="http://docs.python.org/3/library/itertools.html#itertools.groupby" rel="nofollow">^{<cd30>}</a>提供了等效的Python源代码,而且没有那么复杂,所以您只需复制它并将其粘贴到代码中,然后更改它以记住组中最新的值。</p>
<p>最后,如果你觉得迭代器和groupby之类的东西听上去都像希腊语,那么在代码正常工作之前不要试着去做。<a href="http://www.dabeaz.com/generators-uk/" rel="nofollow">Generator Tricks for System Programmers</a>会教你希腊语,所有像这样的问题都是e在你的余生里更爱你。(好吧,除非你被迫用另一种没有生成器的语言写作…)</p>
<hr/>
<p>*我确信您不需要<code>int(number, 10)</code>,因为Python2.7和3.x不会将<code>int('0123')</code>解释为八进制的……但是由于我必须查找它以确保它的可读性,使其显式似乎是一个好主意。在</p>