我有一个简单的轮询器类(下面的代码片段),它根据正则表达式从多个文件夹中检索文件。 我尝试捕获OSError异常并忽略它们,因为文件可能被移出/删除/权限等... 在一些测试中(我在其中创建/删除了大量的文件)我注意到在排序生成器时, 在生成器函数(_get)中引发的异常被重新引发(?),我不得不使用额外的尝试,除了 阻止解决这个问题。
知道为什么会这样吗?所有意见/改进都赞赏!
由于 Timmah
NSURL(string: editedVideoPath)
编辑:感谢@ShadowRanger指出作为sortkey param传递的os.path函数。
答案 0 :(得分:1)
为后代发布答案:根据psychic intuition(和confirmation in the comments),self._sortkey
正在尝试stat
正在排序的文件。虽然对目录具有读取权限足以获取其中包含的文件名,但如果您缺少对这些文件的读取权限,则您将无法stat
这些文件。
由于sorted
正在生成器范围外执行key
函数,因此生成器中没有任何内容引发异常,因此它无法捕获它。您需要预先过滤/预先计算每个文件的stat
值(并删除不能stat
编辑的文件),然后对其进行排序,然后删除(不再相关)stat
数据。例如:
from operator import itemgetter
def with_key(filenames, key):
'''Generates computed_key, filename pairs
Silently filters out files where the key function raises OSError
'''
for f in filenames:
try:
yield key(f), f
except OSError:
pass
# ... skipping to the `sorted` call in get ...
# Replace the existing sorted call with:
# map(itemgetter(1), strips the key, yielding only the file name
files = map(itemgetter(1),
sorted(
# Use with_key to filter and decorate filenames with sortkey
with_key(self._get(maxitems), self._sortkey),
# Use key=itemgetter(0) so only sortkey is considered for
# sorting (making sort stable, instead of performing fallback
# comparison between filenames when key is the same)
key=itemgetter(0), reverse=self._sortreverse))
它基本上是手动执行Schwartzian Transform(又名"装饰 - 排序 - 未装饰")。通常情况下,Python key
/ sorted
的{{1}}参数会隐藏这种复杂性,但在这种情况下,由于可能存在异常,因此需要删除项目一个人出现并希望通过使用EAFP模式来最小化竞争条件),你必须自己完成这项工作。
list.sort
包的2.6-2.7和3.2-3.4)的替代解决方案:你可以避免这个问题(并且在Windows上,只要目录是可读的,并且在类似Windows的文件系统中,在目录条目中缓存文件元数据,在输出中包含不可读的文件)如果你愿意的话复杂性降低,性能可能更好。 Windows上的scandir
(或3.5之前,os.scandir
)可以获取目录条目中缓存的scandir.scandir
信息"免费" (您只需在目录中每千个条目支付一次RTT费用,而不是每个文件支付一次),在Linux上,第一次调用stat
会缓存DirEntry.stat
数据,因此在{{1}中执行此操作这意味着您可以在那里捕获并处理stat
,填充缓存以便在排序期间_get
可以使用缓存数据而不会有OSError
的风险。所以你可以这样做:
self._sortkey
这需要稍微改变用法; OSError
必须在os.DirEntry
instance上运行,而不是文件路径。因此,您可能拥有try:
from os import scandir
except ImportError:
from scandir import scandir
# Prestat will ensure OSErrors raised in _get, not in caller using DirEntry
def _get(self, maxitems=0, prestat=True, follow_symlinks=True):
def customfilter(f):
if self._exclude is not None and self._exclude.search(f):
return False
return self._regex is None or self._regex.search(f)
count = 0
for p in self.paths:
if not os.path.isdir(p): raise PollException("'%s' is not a valid path." % (p,), p)
if maxitems and count >= maxitems: break
try:
# Use scandir over listdir, and since we get DirEntrys, we
# don't need to explicitly use os.path.join to make full paths
# and we can use genexpr for validation instead
for dirent in (de for de in scandir(p) if customfilter(de.name) and self._validate(de.path)):
# On Windows, stat() is cheap noop (returns precomputed data)
# except symlink w/follow_symlinks=True (where it stats and caches)
# On Linux, this will force a stat now, and cache the result
# so OSErrors will only be raised here, not during sorting
if prestat:
dirent.stat(follow_symlinks=follow_symlinks)
if maxitems and count >= maxitems: break
count += 1
yield dirent
except OSError:
'''
There will be instances where we wont have permission on the file/directory or
when a file is moved/deleted before it was yielded.
'''
continue
def get(self, maxitems=0):
# Prestat if we have a sortkey (assuming it may use stat data)
files = self._get(maxitems, prestat=self._sortkey is not None)
if self._sortkey is not None:
# self._sortkey must now operate on a os.DirEntry
# but no more need to wrap in try/except OSError
files = sorted(files, key=self._sortkey, reverse=self._sortreverse)
# To preserve observable public behaviors, return path, not DirEntry
for dirent in files:
yield dirent.path
而不是self._sortkey
。
但它避免了手动Schwartzian变换的复杂性(因为访问冲突只能在self._sortkey = kwargs.get('sortkey', os.path.getmtime)
self._sortkey = kwargs.get('sortkey', lambda de: de.stat().st_mtime)
/ _get
中发生,只要您不这样做更改try
,因此在except
计算期间不会发生OSErrors。通过懒惰地迭代目录而不是在迭代之前构建完整的prestat
(除非目录很大,确实是一个小的好处)并且无需使用key
系统调用,它也可能运行得更快。对于Windows上的大多数目录条目都是如此。