我发现自己使用python来处理很多文件管理脚本,如下所示。在网上寻找示例时,我很惊讶示例中的日志记录和异常处理有多少。每次我写一个新的脚本时,我的意图都不会像下面那样结束,但如果它处理文件,那么无论我的偏执狂接管了什么,最终的结果都不像我在网上看到的例子。因为我是新手,我想知道这是否正常。如果没有,那么你如何处理未知和删除有价值信息的恐惧?
def flatten_dir(dirname):
'''Flattens a given root directory by moving all files from its sub-directories and nested
sub-directories into the root directory and then deletes all sub-directories and nested
sub-directories. Creates a backup directory preserving the original structure of the root
directory and restores this in case of errors.
'''
RESTORE_BACKUP = False
log.info('processing directory "%s"' % dirname)
backup_dirname = str(uuid.uuid4())
try:
shutil.copytree(dirname, backup_dirname)
log.debug('directory "%s" backed up as directory "%s"' % (dirname,backup_dirname))
except shutil.Error:
log.error('shutil.Error: Error while trying to back up the directory')
sys.stderr.write('the program is terminating with an error\n')
sys.stderr.write('press consult the log file\n')
sys.stderr.flush()
time.sleep(0.25)
print 'Press any key to quit this program.'
msvcrt.getch()
sys.exit()
for root, dirs, files in os.walk(dirname, topdown=False):
log.debug('os.walk passing: (%s, %s, %s)' % (root, dirs, files))
if root != dirname:
for file in files:
full_filename = os.path.join(root, file)
try:
shutil.move(full_filename, dirname)
log.debug('"%s" copied to directory "%s"' % (file,dirname))
except shutil.Error:
RESTORE_BACKUP = True
log.error('file "%s" could not be copied to directory "%s"' % (file,dirname))
log.error('flagging directory "%s" for reset' % dirname)
if not RESTORE_BACKUP:
try:
shutil.rmtree(root)
log.debug('directory "%s" deleted' % root)
except shutil.Error:
RESTORE_BACKUP = True
log.error('directory "%s" could not be deleted' % root)
log.error('flagging directory "%s" for reset' % dirname)
if RESTORE_BACKUP:
break
if RESTORE_BACKUP:
RESTORE_FAIL = False
try:
shutil.rmtree(dirname)
except shutil.Error:
log.error('modified directory "%s" could not be deleted' % dirname)
log.error('manual restoration from backup directory "%s" necessary' % backup_dirname)
RESTORE_FAIL = True
if not RESTORE_FAIL:
try:
os.renames(backup_dirname, dirname)
log.debug('back up of directory "%s" restored' % dirname)
print '>'
print '>******WARNING******'
print '>There was an error while trying to flatten directory "%s"' % dirname
print '>back up of directory "%s" restored' % dirname
print '>******WARNING******'
print '>'
except WindowsError:
log.error('backup directory "%s" could not be renamed to original directory name' % backup_dirname)
log.error('manual renaming of backup directory "%s" to original directory name "%s" necessary' % (backup_dirname,dirname))
print '>'
print '>******WARNING******'
print '>There was an error while trying to flatten directory "%s"' % dirname
print '>back up of directory "%s" was NOT restored successfully' % dirname
print '>no information is lost'
print '>check the log file for information on manually restoring the directory'
print '>******WARNING******'
print '>'
else:
try:
shutil.rmtree(backup_dirname)
log.debug('back up of directory "%s" deleted' % dirname)
log.info('directory "%s" successfully processed' % dirname)
print '>directory "%s" successfully processed' % dirname
except shutil.Error:
log.error('backup directory "%s" could not be deleted' % backup_dirname)
log.error('manual deletion of backup directory "%s" necessary' % backup_dirname)
print '>'
print '>******WARNING******'
print '>directory "%s" successfully processed' % dirname
print '>cleanup of backup directory "%s" failed' % backup_dirname
print '>manual cleanup necessary'
print '>******WARNING******'
print '>'
答案 0 :(得分:8)
学会放手(或我如何学会与炸弹一起生活)......
问问自己:你究竟害怕什么,如果它发生了,你将如何处理?在您提供的示例中,您希望避免数据丢失。您处理它的方式是查找您认为是错误的每种条件组合并对其进行大量日志记录。事情仍然会出错,并且不清楚拥有大量日志记录将是处理它的好方法。勾勒出你想要实现的目标:
for each file in a tree
if file is below the root
move it into the root
if nothing went wrong
delete empty subtrees
那么在这个过程中会出现什么样的问题呢?好吧,由于底层文件系统,移动文件操作可以通过多种方式进行barf。我们可以列出所有这些并提供处理它们的好方法吗?不......但总的来说,你将以同样的方式处理它们。有时错误只是一个错误,无论它是什么。
因此,在这种情况下,如果发生任何错误,那么您要中止并撤消任何更改。您决定这样做的方法是创建备份副本并在出现问题时进行恢复。但你最可能的错误是文件系统已满,在这种情况下这些步骤可能会失败......好吧,这是一个常见的问题 - 如果你担心任何一点未知的错误,你如何阻止你的恢复错误的路径?
一般的答案是确保你先做任何中间工作,然后采取一个麻烦(希望是原子)的步骤。在你的情况下,你需要翻转你的恢复。而不是将副本构建为备份,而是构建结果的副本。如果一切都成功,则可以将新结果替换为旧的原始树。或者,如果你真的是偏执狂,你可以为人类留下这一步。这里的优点是,如果出现问题,您可以中止并丢弃您构建的部分状态。
然后您的结构变为:
make empty result directory
for every file in the tree
copy file into new result
on failure abort otherwise
move result over old source directory
顺便说一句,当前脚本中存在一个错误,即此伪代码更明显:如果您在不同分支中具有相同名称的文件,则它们将在新的展平版本中相互覆盖。
关于这个伪代码的第二点是所有的错误处理都在同一个地方(即将make new目录和递归副本包装在一个try块中并捕获它之后的所有错误),这解决了你原来的问题关于日志/错误检查与实际工作代码的大比例的问题。
backup_dirname = str(uuid.uuid4())
try:
shutil.mkdir(backup_dirname)
for root, dirs, files in os.walk(dirname, topdown=False):
for file in files:
full_filename = os.path.join(root, file)
target_filename = os.path.join(backup_dirname,file)
shutil.copy(full_filename, target_filename)
catch Exception, e:
print >>sys.stderr, "Something went wrong %s" % e
exit(-1)
shutil.move(back_dirname,root) # I would do this bit by hand really
答案 1 :(得分:3)
成为一个小偏执狂是可以的。但是有各种各样的偏执:)。在开发阶段,我使用了很多调试语句,所以我可以看到我出错的地方(如果我出错了)。有时我会留下这些语句,但使用一个标志来控制是否需要显示它们(几乎是一个调试标志)。您还可以使用“详细程度”标志来控制您执行的日志记录量。
另一种偏执狂伴随着理智检查。当你依赖外部数据或工具时,这种偏执就会发挥作用 - 几乎任何不是你的程序产生的东西。在这种情况下,偏执绝不会受到伤害(尤其是您收到的数据 - 从不信任)。
如果您正在检查特定操作是否成功完成,那么也可以是偏执狂。这只是正常错误处理的一部分。我注意到你正在执行删除目录和文件等功能。这些操作可能会失败,因此您必须处理失败的情况。如果你只是忽略它,你的代码可能会以不确定/未定义的状态结束,并且可能会做坏事(或至少是不可取的)。
就日志文件和调试文件而言,如果您愿意,可以将它们保留。我通常会执行相当数量的记录;足以告诉我发生了什么。当然,这是主观的。关键是要确保你不要淹没在伐木中;那里有太多的信息,你不能轻易拿出来。一般情况下登录可以帮助您确定当您编写的脚本突然停止工作时出错的地方。您可以通过浏览日志来大致了解问题所在,而不是单步执行程序。
答案 2 :(得分:2)
偏执狂肯定会模糊你的代码试图做的事情。出于几个原因,这是一件非常糟糕的事情。它隐藏了错误。当你需要它来做其他事情时,它会使程序更难修改。这使得调试变得更加困难。
假设Amoss无法治愈你的妄想症,我可以通过以下方式重写这个程序。请注意:
每个包含大量偏执的代码块都被分成了自己的功能。
每次捕获异常时,重新引发,直到它最终被main
函数捕获。这消除了对RESTORE_BACKUP
和RESTORE_FAIL
等变量的需求。
该计划的核心(flatten_dir
)现在只有17行,并且没有偏执狂。
def backup_tree(dirname, backup_dirname):
try:
shutil.copytree(dirname, backup_dirname)
log.debug('directory "%s" backed up as directory "%s"' % (dirname,backup_dirname))
except:
log.error('Error trying to back up the directory')
raise
def move_file(full_filename, dirname):
try:
shutil.move(full_filename, dirname)
log.debug('"%s" copied to directory "%s"' % (file,dirname))
except:
log.error('file "%s" could not be moved to directory "%s"' % (file,dirname))
raise
def remove_empty_dir(dirname):
try:
os.rmdir(dirname)
log.debug('directory "%s" deleted' % dirname)
except:
log.error('directory "%s" could not be deleted' % dirname)
raise
def remove_tree_for_restore(dirname):
try:
shutil.rmtree(dirname)
except:
log.error('modified directory "%s" could not be deleted' % dirname)
log.error('manual restoration from backup directory "%s" necessary' % backup_dirname)
raise
def restore_backup(backup_dirname, dirname):
try:
os.renames(backup_dirname, dirname)
log.debug('back up of directory "%s" restored' % dirname)
print '>'
print '>******WARNING******'
print '>There was an error while trying to flatten directory "%s"' % dirname
print '>back up of directory "%s" restored' % dirname
print '>******WARNING******'
print '>'
except:
log.error('backup directory "%s" could not be renamed to original directory name' % backup_dirname)
log.error('manual renaming of backup directory "%s" to original directory name "%s" necessary' % (backup_dirname,dirname))
print '>'
print '>******WARNING******'
print '>There was an error while trying to flatten directory "%s"' % dirname
print '>back up of directory "%s" was NOT restored successfully' % dirname
print '>no information is lost'
print '>check the log file for information on manually restoring the directory'
print '>******WARNING******'
print '>'
raise
def remove_backup_tree(backup_dirname):
try:
shutil.rmtree(backup_dirname)
log.debug('back up of directory "%s" deleted' % dirname)
log.info('directory "%s" successfully processed' % dirname)
print '>directory "%s" successfully processed' % dirname
except shutil.Error:
log.error('backup directory "%s" could not be deleted' % backup_dirname)
log.error('manual deletion of backup directory "%s" necessary' % backup_dirname)
print '>'
print '>******WARNING******'
print '>directory "%s" successfully processed' % dirname
print '>cleanup of backup directory "%s" failed' % backup_dirname
print '>manual cleanup necessary'
print '>******WARNING******'
print '>'
raise
def flatten_dir(dirname):
'''Flattens a given root directory by moving all files from its sub-directories and nested
sub-directories into the root directory and then deletes all sub-directories and nested
sub-directories. Creates a backup directory preserving the original structure of the root
directory and restores this in case of errors.
'''
log.info('processing directory "%s"' % dirname)
backup_dirname = str(uuid.uuid4())
backup_tree(dirname, backup_dirname)
try:
for root, dirs, files in os.walk(dirname, topdown=False):
log.debug('os.walk passing: (%s, %s, %s)' % (root, dirs, files))
if root != dirname:
for file in files:
full_filename = os.path.join(root, file)
move_file(full_filename, dirname)
remove_empty_dir(dirname)
except:
remove_tree_for_restore(dirname)
restore_backup(backup_dirname, dirname)
raise
else:
remove_backup_tree(backup_dirname)
def main(dirname):
try:
flatten_dir(dirname)
except:
import exceptions
logging.exception('error flattening directory "%s"' % dirname)
exceptions.print_exc()
sys.stderr.write('the program is terminating with an error\n')
sys.stderr.write('press consult the log file\n')
sys.stderr.flush()
time.sleep(0.25)
print 'Press any key to quit this program.'
msvcrt.getch()
sys.exit()
答案 3 :(得分:1)
这对我来说似乎很合理。这取决于您的数据的重要性。
我经常这样开始,并且日志记录是可选的,在文件顶部(或调用者)设置标志设置登录或关闭。你也可能有冗长。
通常,在某些东西工作了一段时间并且不再处于开发状态之后,我会停止阅读日志,并构建我从未阅读过的巨型日志文件。但是,如果出现问题,最好知道它们就在那里。
答案 4 :(得分:0)
如果错误地将作业半完成(仅移动了一些文件),只要没有文件丢失,则不需要备份目录。因此,您可以编写极其简单的代码:
import os, logging
def flatten_dir(dirname):
for root, dirs, files in os.walk(dirname, topdown=False):
assert len(dirs) == 0
if root != dirname:
for file in files:
full_filename = os.path.join(root, file)
target_filename = os.path.join(dirname, file)
if os.path.exists(target_filename):
raise Exception('Unable to move file "%s" because "%s" already exists'
% (full_filename, target_filename))
os.rename(full_filename, target_filename)
os.rmdir(root)
def main():
try:
flatten_dir(somedir)
except:
logging.exception('Failed to flatten directory "%s".' % somedir)
print "ERROR: Failed to flatten directory. Check log files for details."
此处的每个系统调用都会在不破坏您想要保留的数据的情况下取得进展。不需要备份目录,因为您无需“恢复”任何内容。