4

Let me start off by saying: this is to be used for esoteric purposes - not production code. I'm playing around with doing stuff in a single line of Python code, hence my need for expressions and not statements. (EDIT: I'm working on mechanically compiling code to single line of (mostly) equivalent Python code, BitBucket - onelinepython. Note it's very work in progress, hence my reticence in initially mentioning it)

I essentially want to do two things:

  • Call a function that raises an exception instance of my choosing something like:

    raise_exception(WhateverException())

  • Run a function in an enclosed environment where I can get the exception instance that is raised, if one is raised, and, otherwise, the return value of the function that was called. E.g.:

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

Ideally, I want to do this with some default library or built-in function (no matter how much I have to bastardise its intended use). I don't need functions that have the exact same signatures as the examples I provided, just something I can mangle into something close enough. I do have one restriction, though: no use of eval() or equivalent functions.

EDIT: I know I could define my own functions to do this, but then they would still have to follow the restriction that they are a single expression. So solutions that use raise and try inside a function definition are out. Function definitions, raise-statement and try-blocks are unfortunately statements and not expressions.

As for any solutions I've tried. The answer is none yet. The closest I have to an idea of how to solve this is by misusing unittest's assert functionality, but I think that is a dead-end.

EDIT 2: To make it clear, I'm fine with using a module or such that uses raise-statements or try-blocks somewhere in its code. My goal is to take some code and turn it into an equivalent single line of code (which includes any helper functions I may be using). But since I want this to work on a default installation of Python I want to only use default libraries.

JPvdMerwe
  • 3,328
  • 3
  • 27
  • 32
  • Nope this is not homework. It's a personal project of mine. I'm trying to mechanically compile Python code to a single line of Python code. – JPvdMerwe Jun 18 '12 at 19:46
  • 4
    They would never teach this at a school, to be fair... – PinkElephantsOnParade Jun 18 '12 at 19:53
  • I hope note, I cringe when there is mention that first years at my university learn to use `eval()`: all the time. Almost makes me want to take over the course. – JPvdMerwe Jun 18 '12 at 19:54
  • @JPvdMerwe: Okay, the update makes the problem a lot clearer. To be honest, I think this is going to be very hard. Maybe you can hack a solution for CPython with ctypes, but not sure. – Niklas B. Jun 18 '12 at 19:58
  • @NiklasB. I will investigate this, but I've had that same fear. Most Python constructs are simple enough to replicate (if a bit tedious), but this is the only one that I've not thought of a way to get around. – JPvdMerwe Jun 18 '12 at 20:03

4 Answers4

2

To raise an exception:

>>> 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

To catch an exception:

>>> 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>)

Note that the warnings method will only work for exceptions that derive from Warning, but you should always be able to multiply-inherit; here's an example:

>>> 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]
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • Note: tested on Python 2.6, but I can't think why it wouldn't work on 3. – ecatmur Jun 19 '12 at 10:17
  • Your idea for catching exceptions is cool :D I'll have to investigate another way for raising exceptions, because I'd have to be able for re-raising exceptions, if my `try`-block doesn't catch them. If I can't find one, I can just add the limitation that all try blocks must have a catch-all-exceptions case. – JPvdMerwe Jun 19 '12 at 14:05
  • @JPvdMerwe `warnings.warn` will accept any `Warning` instance, so you can rethrow any "uncaught" exception with a little work - you'll lose the traceback, but that wouldn't make much sense anyway after a source transformation. – ecatmur Jun 19 '12 at 14:36
  • I was actually trying to come up with a way to fix tracebacks. It would likely involve a huge table of some sort, mapping from positions in the one line of code to the original code. I think it's possible, but I also think it would be way too much effort for negligible gain. – JPvdMerwe Jun 19 '12 at 21:11
  • This doesn't exactly *catch* exceptions. It allows you to check for them, meaning you need to check after each call you do, which will be extremely slow. (Not to mention double the code size). – Lennart Regebro Jun 20 '12 at 11:51
  • @LennartRegebro I'm not sure of the distinction. As a construct it can do everything a catch does, so you don't need to replace every call, just replace `try` blocks. – ecatmur Jun 20 '12 at 12:12
  • @LennartRegebro, exactly what @ecatmur said. I intend to use this for `try`-blocks or rather a `lambda` containing the code in the `try`-block. I can then simulate the `except`-clauses with a bunch of in-line `if`-statements. – JPvdMerwe Jun 20 '12 at 17:06
  • Note though that what you do here is catch the exception with except. It's just that it's done in a function call that catches all exceptions. So you don't actually need unittest for it, you can implement a function that calls the code you pass in and trap all exceptions with an except statement, and why not just return the exception? Apparently that is "not using except" in your words. :-) – Lennart Regebro Jan 15 '14 at 09:00
  • This construct doesn't let you catch `AssertionError` or `KeyboardInterrupt`. The former might be easily fixable (see `TestCase.failureException`), but the latter doesn't seem to be. – Anders Kaseorg Nov 19 '15 at 22:06
1

You can define your own functions to do this:

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
Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662
  • Alas, it's not that simple. If it were I wouldn't have asked ;) I've updated the question, making my goals clearer. – JPvdMerwe Jun 18 '12 at 19:53
  • 1
    Also I should mention that this implementation will miss some exceptions since the base class for exceptions is `BaseException`. – JPvdMerwe Jun 19 '12 at 08:46
1

This answer suggests that catching exceptions with an expression is not possible in general. I'm also pretty sure that it's not possible to raise an arbitrary exception without using raise. (You can generate some particular exceptions with expressions like 1/0 or dict['keyThatWillNeverExist'], but not any arbitrary exception with arbitrary exception info.)

The language reference says:

The Python interpreter raises an exception when it detects a run-time error (such as division by zero). A Python program can also explicitly raise an exception with the raise statement. Exception handlers are specified with the try ... except statement.

Although this doesn't rule out the possibility that some dark corner of the language specification allows raising exceptions in other ways, the statement is pretty straightforward: you raise exceptions with raise and catch them with try/except.

Note that using unittest, or any other Python library, is unlikely to be a real solution in your sense, because unittest contains functions written in Python that use try/except. So if you're okay with using unittest, you ought to be okay with writing your own functions.

I imagine it might be possible to achieve your goal by "cheating" and writing a C extension that provides functions doing what you want. But that's not really converting it to equivalent Python code.

Community
  • 1
  • 1
BrenBarn
  • 242,874
  • 37
  • 412
  • 384
  • Sadly, this is a project with esoteric goals and hence it will have esoteric restrictions. C-extensions are cheating, I could just as well write a Python module. Which is also right out, since I want the line of code to be self sufficient. I also don't want to use `eval()` cause that would be pointless as the entire project could just be this code: `"eval({})".format(repr(file("input.py").read()))`. But the idea of raising some types of Exceptions could be useful. – JPvdMerwe Jun 18 '12 at 20:26
  • @JPvd: Actually you would need `exec` or `execfile`, since `eval` can also just evaluate a single expression. I think Python is a bit broken in that regard (not even cheating is as easy as you'd like ;) – Niklas B. Jun 18 '12 at 20:40
  • @NiklasB.Thankfully, `exec` is a function in Python 3 unlike Python 2. – JPvdMerwe Jun 19 '12 at 08:47
-1

You are asking how to raise an exception without using raise and catch an exception without using except. Your reluctance to use these statements is because you can't use more than one statement in a single line of code, and you have the idea to compile Python modules into oneliners.

Short answer: Well, you can't.

And even if you could, why would you? It's a completely meaningless effort. The code is not faster or even significantly smaller because it is in one line. It goes against the idea of Python as well. And if you want to obfuscate it, there are much better ways, including compiling it to bytecode.

Longer answer:

You could implement your own exception system, independent of the Python exceptions, but that would be astonishingly slow, and this would still not catch the Python exceptions, so it's not useful in your case.

For the raise-statement, you could re-implementing the raise statement as a function in C, but this you seem to think is cheating, and I also don't see how it would be possible with other statements, such as except.

You could also move out some statements into functions in a separate module, but this is of course then no longer actually a one-liner module in any meaningful way, and not all statements are easily wrapped like this, except being the most relevant case here. You'd have to wrap the whole try/except block, but the resulting function would in turn also only take expressions as parameters, so you would have to extract the blocks into functions, and you'd end up needing to basically re-implement most of Python as a statement-less language, which is silly. And you'd end up with the helper functions in a separate module, which you don't want to.

So the answer to your question of how to raise an exception without using raise and catch an exception without using except is "You don't".

Lennart Regebro
  • 167,292
  • 41
  • 224
  • 251
  • 2
    The point of the project isn't to be useful, nor is supposed to be fast. I'm doing it for the challenge. Although I am aiming to not make it more than a constant factor slower nor use a constant factor more space, but that is for the added challenge alone. This is why I had the disclaimer at the top of my question, so people don't presume I'm doing it for efficiency's sake. This project originally started out with a statement that one line of Python is Turing complete and hence you should be able to write Minesweeper in it. So instead of doing that by hand, I'm doing this. – JPvdMerwe Jun 19 '12 at 13:47
  • Also, I've considered doing an exception system apart from Python's (It would be similar to how I would have to handle return statements in arbitrary places in a function), but I rejected it for the same reasons as you did: It won't allow me to detect exceptions from built-ins. So safe file IO would become atrociously tricky. – JPvdMerwe Jun 19 '12 at 13:50
  • @JPvdMerwe: OK, you are then barking up the wrong tree altogether. That one-line python is turing complete does not mean that you can make the type of translator you are attempting now. A turing machine is a sort of (hypothetical) hardware. Turing complete just means you can simulate that hardware. Instead of a translator you need a compiler. A Python-to-turing-machine compiler. In other words, you need to write a *complete reimplementation of Python*, that runs on a virtual machine, that happens to be Python expressions. – Lennart Regebro Jun 19 '12 at 14:34
  • I'm well aware what Turing Machines are. I'm doing this project to procrastinate doing a maths project on the PSPACE-completeness of TQBF. Also, I'm doing this with the help of Python's `ast` module, so it's not too much work once you figure out how to compile each node type. I originally only wanted to do some subset of Python (seeing as I wanted minesweeper in one line), but I decided doing more could make a nice challenge; along with forcing me to learn Python more intimately. – JPvdMerwe Jun 20 '12 at 17:11
  • Obfuscating Python is certainly one definition of "intimate". But probably not a very useful one. :-) And if you know what Turing machines are, you'll also know that Python expressions being Turing complete are irrelevant for what you are doing now. – Lennart Regebro Jun 20 '12 at 17:51