Convert any* Python 2 script into a single line.
No newlines allowed. No semicolons, either. No silly file I/O tricks, or eval or exec. Just good, old-fashioned λ.
Yeah, I know, I’m a terrible person.
That would be against the spirit of this exercise. Why pass up a perfectly good excuse to abuse lambda functions, ternary expressions, list comprehensions, and even the occasional Y combinator? Important life advice: Never pass up an opportunity to use the Y combinator.
This transpiler converts any Python script into a single line of code, supporting the following features:
*Not implemented for now, but theoretically possible: from module import *. Not implemented, and open problems: yield and with.
“Not only is it an extremely cool puzzle with a nice and lucid explanation of how it works, but something I occasionally use in my professional life. For example, it allows smuggling statements into places where only expressions are required (e.g. some debuggers and evaluators), or embedding multi-line statements into hostile environments that strip indentation.
“Bonus point is the seemingly casual usage of the Y combinator in a practical application. Kudos.”
—Hrvoje Nikšić, author of GNU Wget
For a deeper dive into lambda calculus, I recommend:
One ultimate test is whether such a Python script can be run on itself. Behold main_ol.py, a Python script that provides the same compiler functionality; the oneliner-izer itself oneliner-ized.
This script will rewrite your Python 2 code as a single line.
(Spoiler warning! You may wish to look at the puzzles below, first.)
Puzzles? Puzzles! Here I present some of the challenges I had to solve while creating this, so that you can take some time to figure out its inner workings yourself.
The inspiration for this project came from MIT’s nth Annual Python Bee, in which contestants are given a function to write and must spell out their code out loud, character-by-character, without looking. As I was competing, I noticed that many of the problems could be solved in one line, via judicious application of list comprehensions and lambda functions. Here’s an example of such a problem.
Problem 0. Write a function f that takes in a string and returns True if and only if that string is comprised only of the characters ‘a’ and ‘A’.
Of course, there are normal ways to solve this problem...
def f(string): for char in string: if char != 'a' and char != 'A': return False return True
...but normal isn’t what we’re looking for here. You can solve this problem using a single line of Python. How?
f = lambda string: not False in [char in 'Aa' for char in string]
f = lambda string: all([char in 'Aa' for char in string])
As this example shows, a list comprehension can sometimes be used instead of a for loop in order to achieve the same goal. A question arises: Might we be able to replace any for loop with a list comprehension?
In fact, might we be able to replace any Python code whatsoever with a single line of code?
This is where our quest begins.
Suppose, for now, that we’re only allowing one print statement at the end of whatever Python code will be one-lined.
Problem 1. Determine how to convert a block of code (that is, multiple statements in sequence) into a single line.
For example, can you find a simple, systematic way of transforming something like the following into one line of code? You should only use two ‘+’ symbols.
a = 10 + 10 print a + a
Hint. Can you provide some mechanism by which the values of variables may be saved from one part of the code execution to the next, even though execution isn’t separated into lines?
print (lambda a: a + a)(10 + 10)
Problem 2. Determine how to allow function definitions in blocks of code: How can you convert the following into a single line of code?
def f(x): return x*4 print f(3)
print (lambda f: f(3))(lambda x: x*4)
Can your method for function definitions be extended to permit the use of *args and/or **kwargs?
def f(x, *args, **kwargs): return (x, args, kwargs) print f(5, 6, 7, foo=3)
Answer. Conveniently, lambda functions can take in *args and **kwargs as well:
print (lambda f: f(5, 6, 7, foo=3))(lambda x, *args, **kwargs: (x, args, kwargs))
Problem 3. Determine how to allow statements which only act via side effects: they do not set a variable to a new value.
do_something() print 42
Answer. Since the output value of do_something() isn’t used, we can funnel it to the unused variable _.
print (lambda _: 42)(do_something())
We can use the above trick to remove our assumption that all print statements come at the end of a file: we can instead treat print like a function with side effects; it can be called anywhere in the code.
For now, assume that print is available to us as a function named __print(). Working in Python 2, we’ll need another trick (described later) to define this function without violating the one-line constraint.
Let’s move on to the real interesting stuff: control flow.
Problem 4. if and else statements. How can you express the following in one line of code?
if True: x = 5 else: x = 10 print x * 100
Hint. Consider reading about Python’s ternary operator.
Answer. Once we branch at the if, we need some mechanism with which we can join the two branches back together, but with the program state (in this case, only the variable x) having been modified by each branch. In pseudocode, something like this:
def continuation(x): print x * 100 if True: x = 5 continuation(x) else: x = 10 continuation(x)
As one line, something like this:
(lambda continuation: (lambda x: continuation(x))(5) if True else (lambda x: continuation(x))(10))(lambda x: (lambda _: None)(__print(x*100)))
Problem 5. while loops. How can you express the following in one line of code?
x = 5 while x < 20: x = x + 4 print x
Hint. The pseudocode for this solution looks similar to the if/else pseudocode from above. One branch goes back to the top of the while loop, while the other branch continues on with whatever happens after the while loop.
Hint. In order to implement this, we need recursion. The code for the while loop must call itself as its own continuation.
Answer. The start and end of the while loop can be thought of as continuations to call after the program state has been modified. In this case, the program state consists only of the one variable (x) that has been modified.
x = 5 def while_loop(x): if x < 20: x = x + 4 while_loop(x) else: print x while_loop(x)
Usually, it’s simple for Python functions to be recursive – by the time a recursive Python function has been executed, it has already been defined, and can therefore call itself without incident. However, since we’re using lambda to define our functions as anonymous functions, we don’t have this luxury. The function while_loop here needs to be self-referential, but since it’s anonymous, we can’t reference it by name.
Using the Curry-Rosenbloom Y combinator (often simply referred to as “the Y combinator”), we can give the function for our while loop access to itself as a subroutine, allowing it to be recursive.
Y = (lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args))))
while_loop = Y( lambda while_loop: ( lambda x: (lambda x: while_loop(x))(x+4) if x < 20 else __print(x) ) )
(lambda x: while_loop(x))(5)
Finally, our completely one-lined original code becomes as follows:
(lambda Y: (lambda while_loop: (lambda x: while_loop(x))(5))(Y(lambda while_loop: (lambda x: (lambda x: while_loop(x))(x+4) if x < 20 else __print(x)))))(lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args))))
Problem 6. For loops. How can you convert the following into one line?
x = 0 for item in iterable: x += item print x [...]
Answer. You might think that you would be able to reduce the for loop to an equivalent while loop by counting up using some index i and indexing into iterable[i], but it’s not that easy.
The object which a Python for loop iterates over must be an iterable; to satisfy this, it needs to support either the __getitem__ method or the __iter__ method. If only the __iter__ method is defined, it will be valid to loop over the item, but it won’t be valid to index into it:
>>> class Iterable: ... def __iter__(self): ... for i in range(5): ... yield i ... >>> iterable = Iterable() >>> x = 0 >>> for item in iterable: # Iterating works just fine. ... x += item ... print x ... 0 1 3 6 10 >>> Iterable # Indexing doesn't. Traceback (most recent call last): File “<stdin>”, line 1, in <module> AttributeError: Iterable instance has no attribute '__getitem__'
Instead of using a while loop, we can use reduce to iterate over the items in the iterable, applying a function which will modify the current program state (in this case, only the variable x) according to the current item.
One-lined, this yields:
(lambda x: [...])(reduce((lambda x, item: (lambda x: (lambda _: x)(__print(x)))(x + item)), iterable, x))
Problem 7. How can you incorporate import statements into a one-lined program? For example, try to one-line the following:
import random as rnd print rnd.choice([1, 2, 3, 10])
Answer. Any import statement invokes the built-in Python function __import__(), which we can use to implement this functionality. For example, we can rewrite the above so that the import becomes an assignment:
rnd = __import__('random') print rnd.choice([1, 2, 3, 10])
As one line, this is:
print (lambda rnd: rnd.choice([1, 2, 3, 10]))(__import__('random'))
Problem 8. How can you make it so that print is accessible as a function (stored to the variable __print), so that we can call __print(whatever) from anywhere in our giant single line?
Answer. In Python 2, usually we can use from __future__ import print_function in order to get print as a function. However, this isn’t a simple import statement, and it won’t work to import it as shown in Problem 7. The from __future__ import print_function line is a future statement, a special directive to the compiler that must appear near the top of a file.
Instead, there’s a bizarre hack we can do using __builtin__.
__builtin__ = __import__('__builtin__') __print = __builtin__.__dict__['print'] [...]
Using this trick, the one-lined code is as follows:
(lambda __builtin__: (lambda __print: [...])(__builtin__.__dict__['print']))(__import__('__builtin__'))
That’s it! There are still some open problems to solve, but with these tricks we now have the essence of a system for converting Python code into a single line. Using the ast module, these techniques are sufficient to form the backbone of the transpiler.
Details behind the more advanced techniques – such as exception handling – can be found in the talk, below.