意外的NameError
我正在写一个脚本,用来批量重命名文件夹里的所有文件。我想让这个脚本模块化,这样核心算法(也就是生成新文件名的部分)就能很方便地替换。
这是我目前的进展:
from os import listdir, rename
def renamer(path, algorithm, counter=False, data=None, data2=None, safe=True):
call_string = 'new=algorithm(i'
if counter:
call_string += ', file_index'
if data != None:
call_string += ', data'
if data2 != None:
call_string += ', data2'
call_string += ')'
if safe:
print('Press Enter to accept changes. '+
'Type anything to jump to the next file.\n')
files_list = listdir(path)
for i in files_list:
file_index = files_list.index(i)
old = i
exec(call_string)
if safe:
check = input('\nOld:\n'+old+'\nNew:\n'+new+'\n\nCheck?\n\n')
if check is not '':
continue
rename(path+old, path+new)
return
现在有个原因让我感到困惑(我觉得没法解释),调用这个函数时出现了NameError错误:
>>> def f(s):
return 'S08'+s
>>> path='C:\\Users\\****\\test\\'
>>> renamer(path, f)
Press Enter to accept changes. Type anything to jump to the next file.
Traceback (most recent call last):
File "<pyshell#39>", line 1, in <module>
renamer(path, f)
File "C:\Python32\renamer.py", line 25, in renamer
check = input('\nOld:\n'+old+'\nNew:\n'+new+'\n\nCheck?\n\n')
NameError: global name 'new' is not defined
之所以困惑,是因为在第25行的时候,应该已经执行了call_string,这样就定义了new这个名字。我已经花了一个多小时在找我的错误,逐行把代码输入到命令行里测试了两遍,结果都没问题,我就是搞不清楚问题出在哪。
有没有人能帮我找出我哪里出错了?
编辑:我已经猜测可能是用exec无法赋值给名字,所以我进行了如下测试,结果是成功的:
>>> exec('cat="test"')
>>> cat
'test'
4 个回答
你不需要用到 exec
。你可以调整传给函数的参数,这样它就能适应不同的情况:
def renamer(path, algorithm, counter=False, data=None, data2=None, safe=True):
if safe:
print('Press Enter to accept changes. '+
'Type anything to jump to the next file.\n')
files_list = listdir(path)
for file_index, old in enumerate(files_list):
opt_args = []
if counter:
opt_args.append(file_index)
if data is not None:
opt_args.append(data)
if data2 is not None:
opt_args.append(data2)
new = algorithm(old, *opt_args)
if safe:
check = input('\nOld:\n'+old+'\nNew:\n'+new+'\n\nCheck?\n\n')
if check:
continue
rename(path+old, path+new)
还有一些小建议:用“is not None”来代替“!= None”;要检查一个字符串是否为空,只需用“if check”;而且在函数的最后不需要单独写一个空的 return。我还加入了 @gurney alex 提出的 enumerate
的改进建议。
你在exec
调用里面声明了一个名字叫new
的变量。这个变量在外面是看不见的。所以当你在exec
调用之后尝试访问new
时,就会出现错误,而在exec
内部是没有问题的。
我觉得你根本不需要在这里使用exec
。你构建call_string
的方式,其实可以直接调用algorithm
。
如果你真的想让你的算法能够接受可变参数,可以使用关键字参数。
不要使用exec或eval来做这个,直接写就行。
new = algorithm(i, file_index, data, data2)
确保你所有的算法都能使用这4个参数(不需要的可以忽略)。
如果你不喜欢这样,下面的方法比使用eval更符合Python的风格,也更高效:
args = [i]
if counter:
args.append(file_index)
for arg in (data, data2):
if arg is not None:
args.append(arg)
new = algorithm(*args)
另外,把这个丑陋的写法:
for i in files_list:
file_index = files_list.index(i)
替换成
for index, filename in enumerate(file_list):
...
最后,使用os.path.join来连接路径的各个部分,而不是简单地拼接字符串。这样可以避免在调用函数时,如果目录名后面没有'/'而导致的调试麻烦。