在Python 3中,表达式中引发和捕获异常

4

首先声明:此内容仅用于奥秘目的,不适用于生产代码。我正在尝试在一行Python代码中完成一些操作,因此需要表达式而非语句。(编辑:我正在开发机械化编译代码到单行(大部分)等价Python代码的工具BitBucket - onelinepython。请注意它还处于非常初级的阶段,因此我最初提到时有所保留)

我想要做两件事情:

  • 调用一个函数来引发我选择的异常实例,例如:

    raise_exception(WhateverException())

  • 在封闭的环境中运行一个函数,在该环境中,如果引发了异常实例,则可以获取该实例,否则可以获取被调用函数的返回值。例如:

    has_exception, return_or_exception = check_exception(f, param1, param2, ...)

理想情况下,我希望能够使用一些默认库或内置函数来实现这个目标(无论我需要多少改变其原本的用途)。我不需要与我提供的示例具有完全相同签名的函数,只需要将其转换为足够接近的形式即可。但是我有一个限制:不能使用eval()或等效函数编辑:我知道我可以定义自己的函数来解决这个问题,但是它们仍然必须遵循它们是单个表达式的限制。因此,在函数定义中使用raise语句和try块的解决方案被排除了。函数定义、raise语句和try块不幸地是语句而不是表达式。
至于我尝试过的任何解决方案,答案是没有。我最接近解决这个问题的想法是误用unittest的断言功能,但我认为那是走不通的。

编辑 2:为了明确,我可以使用一个使用raise-语句或try-块的模块或类。我的目标是将一些代码转换成等效的单行代码(包括我可能使用的任何辅助函数)。但是,由于我希望它能在Python的默认安装上运行,因此我只想使用默认库。


不,这不是作业。这是我个人的项目。我正在尝试将Python代码机械地编译成单行Python代码。 - JPvdMerwe
4
说实话,他们在学校里不会教这个... - PinkElephantsOnParade
请注意,每当有人提到我所在大学的一年级学生都要一直使用eval()时,我都会感到非常不安。这几乎让我想接管这门课程。 - JPvdMerwe
@JPvdMerwe:好的,更新后问题变得更加清晰了。老实说,我认为这将会非常困难。也许你可以使用ctypes来破解CPython的解决方案,但不确定。 - Niklas B.
@NiklasB。我会调查这个问题,但我也有同样的担忧。大多数Python结构都足够简单,可以复制(虽然有点乏味),但这是唯一一个我没有想到解决方法的结构。 - JPvdMerwe
4个回答

2

抛出异常的方法:

>>> import warnings
>>> WV = type("WV", (Warning, ValueError), {})
>>> warnings.simplefilter("error", WV)
>>> warnings.warn("wv", WV)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
__main__.WV: wv

捕获异常:

>>> import unittest
>>> res = type("TR", (unittest.TestResult, ), dict(addError=lambda self, test, err: setattr(self, '_last_err', err)))()
>>> unittest.FunctionTestCase(lambda: [][0])(res)
>>> res._last_err
(<type 'exceptions.IndexError'>, IndexError('list index out of range',), <traceback object at 0x2b4358e69950>)

请注意,warnings方法仅适用于派生自Warning的异常,但您应该始终能够进行多重继承;以下是一个示例:
>>> WS = type("WS", (Warning, StopIteration), {})
>>> warnings.simplefilter("error", WS)
>>> list(type("R", (object,), dict(__init__=lambda self, stop: (setattr(self, 'stop', stop), setattr(self, 'i', 0), None)[-1], __iter__=lambda self: self, next=lambda self: (self.i, setattr(self, 'i', self.i + 1))[0] if self.i < self.stop else warnings.warn("Stop", WS)))(5))
[0, 1, 2, 3, 4]

注意:已在Python 2.6上测试,但我想不出为什么它不能在3上工作。 - ecatmur
你捕获异常的想法很棒 :D 我得研究另一种引发异常的方式,因为如果我的 try 块无法捕获它们,我必须能够重新引发异常。如果找不到其他方法,我可以添加一个限制,即所有 try 块都必须有一个 catch-all-exceptions 情况。 - JPvdMerwe
@JPvdMerwe warnings.warn 将接受任何 Warning 实例,因此您可以通过一些工作重新抛出任何“未捕获”的异常 - 您将失去回溯,但在源代码转换之后这也没有太多意义。 - ecatmur
请注意,在这里所做的是使用 except 来捕获异常。只不过它是在一个函数调用中捕获所有异常。因此,你实际上不需要使用 unittest,你可以实现一个函数来调用传递进来的代码并使用 except 语句捕获所有异常,为什么不直接返回异常呢?显然,在你的话中,“不使用 except”。 :-) - Lennart Regebro
这个结构不允许你捕获AssertionErrorKeyboardInterrupt。前者可能很容易修复(请参见TestCase.failureException),但后者似乎无法解决。 - Anders Kaseorg
显示剩余4条评论

1
您可以定义自己的函数来完成此操作:
def raise_exception(ex):
    raise ex

def check_exception(f, *args, **kwargs):
    try:
        return False, f(*args, **kwargs)
    except Exception as e:
        return True, e

唉,事情并不那么简单。如果是的话,我就不会问了 ;) 我已经更新了问题,让我的目标更清晰了。 - JPvdMerwe
1
另外我应该提到,由于异常的基类是BaseException,所以这个实现会忽略一些异常。 - JPvdMerwe

1

这个答案表明,通常情况下无法使用表达式捕获异常。我也非常确定,如果不使用raise,就无法引发任意异常。(您可以使用像1/0dict['keyThatWillNeverExist']这样的表达式生成某些特定的异常,但不能使用任意异常和任意异常信息引发异常。)

语言参考中说:

当Python解释器检测到运行时错误(例如除以零)时,会引发异常。Python程序也可以使用raise语句显式地引发异常。异常处理程序由try ... except语句指定。

虽然这并不排除语言规范的某些黑暗角落允许以其他方式引发异常的可能性,但该语句非常直接:您使用raise引发异常,并使用try/except捕获异常。

请注意,使用unittest或任何其他Python库不太可能是您的真正解决方案,因为unittest包含使用try/except编写的Python函数。因此,如果您可以使用unittest,那么编写自己的函数也应该没有问题。
我想通过“作弊”并编写一个C扩展来实现您的目标可能是可能的,该扩展提供执行您想要的操作的函数。但这并不是将其转换为等效的Python代码。

遗憾的是,这个项目有着神秘的目标,因此它将有神秘的限制。C扩展是作弊行为,我可以写一个Python模块来实现同样的功能。但这也不行,因为我希望代码行具有自给自足性。我也不想使用eval(),因为那样毫无意义,整个项目可能只是这行代码:"eval({})".format(repr(file("input.py").read()))。但引发某些类型的异常的想法可能会有用。 - JPvdMerwe
实际上你需要使用exec或者execfile,因为eval也可以只评估单个表达式。我认为Python在这方面有点有问题(甚至作弊也不像你想的那么容易;) - Niklas B.
谢天谢地,在 Python 3 中 exec 是一个函数,不像 Python 2。 - JPvdMerwe

-1

您正在询问如何在不使用raise的情况下引发异常,并在不使用except的情况下捕获异常。您不愿使用这些语句是因为您不能在单行代码中使用多个语句,并且您有将Python模块编译成单行代码的想法。

简短的回答:嗯,你不能。

即使您可以,为什么要这样做呢?这是完全没有意义的努力。代码不会更快,甚至不会显着变小,因为它在一行中。这也违背了Python的理念。如果您想混淆它,还有更好的方法,包括将其编译为字节码。

更长的回答:

您可以实现自己的异常系统,独立于Python异常,但这将非常慢,并且仍然无法捕获Python异常,因此在您的情况下没有用处。

对于raise语句,您可以将其重新实现为C函数,但您似乎认为这是作弊,而且我也不知道如何处理其他语句,例如except

你还可以将一些语句移到独立模块中的函数中,但这实际上不再是一个有意义的单行模块了,也不是所有语句都容易包装成函数的形式,其中最相关的情况是 `except`。你必须包装整个 `try/except` 块,但由此产生的函数也只能接受表达式作为参数,因此你必须将块提取到函数中,并最终需要基本上重新实现 Python 作为一个无语句的语言,这是徒劳的。而且你会得到一个在独立模块中的帮助函数,这是不想要的。
因此,如何在不使用 `raise` 抛出异常并不使用 `except` 捕获异常的答案是“你不能”。

2
这个项目的目的不是为了实用,也不是为了速度。我做这个项目是为了挑战自己。虽然我希望它不会比常数因子更慢,也不会使用更多的空间,但这只是为了增加挑战。这就是为什么我在问题的顶部放置了免责声明,以便人们不会认为我是为了效率而这样做。这个项目最初是从一个陈述开始的,即 Python 的一行代码是图灵完备的,因此你应该能够在其中编写扫雷游戏。所以,我正在做这个项目,而不是手工完成。 - JPvdMerwe
此外,我考虑过使用与Python不同的异常处理系统(类似于在函数中任意位置处理返回语句),但出于与您相同的原因,我拒绝了它:它无法让我检测到内置异常。因此,安全文件IO将变得非常棘手。 - JPvdMerwe
@JPvdMerwe:好的,你现在完全走错了方向。那个一行Python代码是图灵完备并不意味着你可以制作出你现在尝试的那种类型的翻译器。图灵机是一种(假设的)硬件。图灵完备只是意味着你可以模拟那个硬件。你需要的不是一个翻译器,而是一个编译器。一个Python到图灵机的编译器。换句话说,你需要编写一个完整的Python重新实现,它运行在一个虚拟机上,这个虚拟机恰好是Python表达式。 - Lennart Regebro
我非常清楚图灵机是什么。我正在做这个项目来拖延做一个关于TQBF的PSPACE完备性的数学项目。此外,我正在使用Python的ast模块来完成这个项目,一旦你弄清楚如何编译每个节点类型,工作量就不会太大。起初,我只想做Python的一些子集(因为我想在一行中玩扫雷),但我决定做更多的事情可能会成为一个不错的挑战;同时迫使我更深入地学习Python。 - JPvdMerwe
混淆Python肯定是“亲密”的一种定义。但可能并不是非常有用的。 :-) 如果你知道图灵机是什么,你也会知道Python表达式是否是图灵完备的对你现在所做的事情来说是无关紧要的。 - Lennart Regebro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接