意外的NameError

3 投票
4 回答
650 浏览
提问于 2025-04-16 21:45

我正在写一个脚本,用来批量重命名文件夹里的所有文件。我想让这个脚本模块化,这样核心算法(也就是生成新文件名的部分)就能很方便地替换。

这是我目前的进展:

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 个回答

1

你不需要用到 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 的改进建议。

1

你在exec调用里面声明了一个名字叫new的变量。这个变量在外面是看不见的。所以当你在exec调用之后尝试访问new时,就会出现错误,而在exec内部是没有问题的。

我觉得你根本不需要在这里使用exec。你构建call_string的方式,其实可以直接调用algorithm

如果你真的想让你的算法能够接受可变参数,可以使用关键字参数

3

不要使用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来连接路径的各个部分,而不是简单地拼接字符串。这样可以避免在调用函数时,如果目录名后面没有'/'而导致的调试麻烦。

撰写回答