在Python中,错误未在不正确包中被捕获

4 投票
3 回答
1190 浏览
提问于 2025-04-15 17:31

编辑:

好的,我成功找到了这个问题的根源,并且有了完整的代码来重现它。不过,这看起来要么是设计上的问题,要么是Python的一个bug。

首先,创建两个兄弟包:adminGeneral,每个包里当然都要有自己的 __init__.py 文件。

admin 包里放一个名为 'test.py' 的文件,里面写入以下代码:

from General.test02 import run
import RunStoppedException
try:
    run()
except RunStoppedException.RunStoppedException,e:
    print 'right'
except Exception,e:
    print 'this is what i got: %s'%type(e)

同时在 admin 包里放一个名为 'RunStoppedException.py' 的文件,里面写入以下代码:

class RunStoppedException(Exception):
    def __init__(self):
        Exception.__init__(self)

General 包里放一个名为 'test02.py' 的文件,里面写入以下代码:

import admin.RunStoppedException
def run():
    raise admin.RunStoppedException.RunStoppedException()

打印出来的结果是:

this is what i got: <class 'admin.RunStoppedException.RunStoppedException'>

而应该是 right。这个问题只发生在一个文件和异常文件在同一个目录下时,所以它们的导入方式不同。

这是设计上的问题,还是Python的bug呢?

我使用的是Python 2.6,在eclipse+pydev环境下运行。

3 个回答

0

我觉得可能有两个原因

  1. 你有两个不同的异常类,但名字是一样的
    补充说明: 我觉得问题出在这里,因为你用两种方式导入了异常类

    • from RunStoppedException import RunStoppedException
    • from admin.RunStoppedException import RunStoppedException

    把它们统一一下,你的问题就解决了。

  2. 你可能在使用某个开发工具(IDE),它干扰了你的代码,这听起来有点奇怪,但如果你还没试过的话,可以在命令行上运行你的代码试试看。

即使第一个和第二个方法都不能解决你的问题,写一小段代码来展示这个问题,我们可以在这里运行并修复它。不过我相信你不需要这样,因为一旦你写出这样一个小的独立脚本来复现问题,你自己也会找到解决办法。

0

对我来说一切正常:

[/tmp] ls admin/
RunStoppedException.py  __init__.py     test.py
RunStoppedException.pyc __init__.pyc
[/tmp] ls General/
__init__.py __init__.pyc    test02.py   test02.pyc
[/tmp] python -m admin.test 
right
[/tmp] 

运行环境:

Python 2.6.4 Stackless 3.1b3 060516 (release26-maint, Dec 14 2009, 23:28:06) 
[GCC 4.2.1 (Apple Inc. build 5646) (dot 1)] on darwin

我猜你的路径上可能有另一个叫“General”的东西,可能是之前测试时留下的,这就是为什么出现的错误不匹配的原因。你有没有尝试过用 idinspect.getabsfile 来调试一下?如果试过,结果是什么呢?

7
import admin.RunStoppedException

这是一个模糊的相对导入。你是想从顶层模块 admin 导入 RunStoppedException 吗?还是在一个包内从 mypackage.admin 导入?如果你当前的工作目录(也就是 Python 查找模块的地方)恰好在这个包里面,那么这两者都有可能,具体取决于你是怎么运行这个脚本的,Python 是否知道它在一个包里面。

如果你在不同的模块中同时使用了 import admin.RunStoppedExceptionimport RunStoppedException,那么可能会导入同一个模块的两个副本:一个是顶层的 RunStoppedException,另一个是包 admin 的子模块 admin.RunStoppedException。这样就会导致两个异常实例,后面在 except 处理时就会出现不匹配的问题。

所以不要使用隐式相对导入。无论如何,这种方式都要被淘汰(可以参考 PEP328)。总是要写出完整的模块名称,比如 import mypackage.admin.RunStoppedException。不过要避免将模块名和类名用同样的名称,这样会让人很困惑。需要注意的是,Python 允许你这样写:

except RunStoppedException:

这里的标识符指的是一个模块,而不是异常的子类。这是历史原因造成的,可能将来也会被改变,但目前这样做可能会隐藏一些错误。一个常见的做法是使用 mypackage.exceptions 来存放多个异常。每个类放一个文件是 Java 的习惯,但在 Python 中并不被推荐。

一般来说,尽量减少导入模块内容(比如类)也是个好主意。如果某个地方修改了模块内的 RunStoppedException 的副本,你在不同的脚本中就会有不同的副本。虽然类通常不会改变,但模块级的变量可能会,而当你把东西从它所属的模块外部使用时,猴子补丁和重新加载就会变得更加困难。

撰写回答