Nerd Paradise
Home
About
Puzzles
Math
Programming
Origami
Japanese
MSPaint
Home > Programming > Writing Long Python Scripts in One Line

Writing Long Python Scripts in One Line

Disclaimer: this post has no practical application. It's just a fun way of thinking about coding differently.

One thing that separates Python from most modern widespread programming languages is that whitespace does matter. There are no curly braces to denote where the blocks are. This is done with indention instead. There are no semicolons to denote where the end of each command is. This is done with newlines.


Sometimes this can be frustrating, but it does enforce good code-writing style.

It also creates an opportunity for perverse fun: attempting to get a Python program down to 1 line of code.

Python is pretty simple to learn, but there are some weird hooks in it that allow you to twist it in ways it wasn't meant to be used. For example, earlier I posted a bit on how to "spoof" a switch statement in Python using a lists hack. Let's raise this kind of idea to the power of steroids.

Consider the following program that takes in a string and a list of numbers. It will prepend the string to each of the numbers and return a string of this new list separated by commas. For example, foo('$', range(5)) would return '$0, $1, $2, $3, $4'. This is how we would normally write something like that...

def foo(string, numbers):
  output = ''
  for i in range(len(numbers)):
    if i > 0:
      output += ', '
    output += string + str(numbers[i])
  return output


This can be taken down to one line as follows:

def foo(string, numbers): return ', '.join(map(lambda s,n:s+str(n), [string for i in numbers], numbers))


There's a function called map, that takes in a function pointer and a series of lists of equal size. An item from each list is passed as a parameter to a call of the function pointer you passed in as the first parameter. Suppose you passed in a function called 'bar' and 2 lists with 10 items in it. Map would return a list with 10 items in it where the first item is bar invoked with the first item from the first list as its first parameter and the first item of the second list as its second parameter. The 2nd item in the returned list would be the 2nd item of the first list and the 2nd item of the second list and so on.

Since all lists passed to map have to be the same size, we'd need a list of the string we passed in n times, where n is the size of the numbers list. To do this, there's a wacky implementation of the for loop that is rarely used...

strings = [string for i in range(len(numbers))]


If '$' was the string, we now have ['$', '$', '$', ...] with as many '$'s as there are items in the numbers list.

Strings also have a method called .join that takes in a list of strings. This will return a single string that is the list you passed in, but concatenated together with the original string as the glue.

Python supports lambda's. Lambdas are nameless functions. This is ideal for map, since you don't really have to name the function anything to pass its pointer to map. Once map is done, you won't care about this function ever again. Lambda's one-time-deal-ness is perfect. The lambda in this exampl takes 2 parameters: s and n (which correspond to the string list and the numbers list). It adds the string version of n to the end of s.

And last but not least, if a colon is followed by 1 line of indented code, then you can put those two lines of code on the same line. However, this rule can only be applied once. Suppose I have a 3-line program where the first line is a def, the 2nd line is a for loop declaration, and the 3rd line is a normal statement. You can put the 3rd line on the same line as the 2nd line, but even though this is now technically "one line" below the first colon, you can't put this new line on the same line as the first line.

Optional Parameters

If you need to start out with some variables, you can always rely on optional parameters. Simply put all the initial values you need in the parameter list, but follow them with an equals sign with the value you want to start them out as.

def foo(a, b=3, c=open('whatever.txt''rt')):
  ...


In this example, you'll have a as the first and only parameter you passed in to the function call, but you'll also have b starting out as 3 and c starting out as a file read stream thingy.

More Python Abuse: Recursion


If you're dealing with something where you must assign a value to a variable and use that reference later on, then recurions is the way to go if optional parameters won't cut it.

Consider the following program that simply takes in a number and creates a text file with that number as its name and contents:

def foo(a):
  con = open(str(a)+'.txt''wt')
  con.write(str(a))
  con.close()


Now we have a problem. We must create a file write stream that we must reference twice in order to write to it and close it. Also, we can't use optional parameters to open the stream because the filename is in terms of one of the parameters passed in.

Let's rewrite the program with recursion and if statements. Just for the hell of it...

def foo(a, counter=0, con=None):

  if counter == 0:
    foo(a, 1, open(a + '.txt''wt'))
  
  if counter == 1:
    con.write(str(a))
    con.close()


Excellent. I like to call this Blake O'Hare Normal Form (simply because I have no idea what the hell to call this state and I like seeing my name everywhere). When you get a program in Blake O'Hare normal form, it can be guaranteed that there is a way to condense it to one line. I don't know if this has a real name already, so for now, I'll just keep calling it Blake O'Hare Normal Form.

Blake O'Hare Normal Form:


  • All statements that require a previous statement to be executed are separated by if statements.
  • The function has a counter as a parameter that is modified to determine which if statement will be ran on the next invocation of the function.
  • The program uses recursive calls to change the counter.

So to get rid of the if statements, we convert each line of code into an item in a giant list. If foo returns anything, then the list ends with [counter] to indicate which commands to return.

def foo(a, counter=0, con=None):

  (
    foo(a, 1, open(a + '.txt''wt')),
    
    (
      con.write(str(a)),
      con.close()
    )
  )


I added whitespace to increase readability for this example.

Before, items under false if statements were not executed, but now they are. Therefore this doesn't immediately work. We need to do some odd tinkering with the function pointers and methods...

def foo(
  a, 
  counter=0
  con=None
  close=lambda x:x.close(), 
  write=lambda x,s:x.write(s),
  n=lambda a=0,b=0,c=0:0
  ):

  (
    (foo,n)[counter](a, 1, (open,n)[counter](a + '.txt''wt')),
    
    (
      (n,write)[counter](con, str(a)),
      (n,close)[counter](con)
    )
  )


(Even more whitespace added, otherwise this would be impossible to read)

I did a few things here. Rather than having a function call in the form of function(parameter, parameter, ...), instead I now have them in the form of (function, fakefunction)[counter](parameter, parameter, ...) so now, before a function is invoked, it first selects a function pointer from the first tuple taking into consideration the counter. In this case, I have defined n as a lambda that takes in up to 3 parameters that does more or less nothing. This is crucial, because if the foo on the first real line of code was executed each time the function would run, this would cause an infinite recursive decent. That is bad. Here, foo is only invoked when counter is 0. When counter is 1, then those 3 parameters are passed to my dumb lambda. The same idea is also applied to open(a + .'txt', 'wt'). Instead of creating a worthless 2nd file connection during the 2nd invocation of foo, this function call is also escaped out, such that a+'.txt' and 'wt' are passed to the dumb lambda on the 2nd invocation of foo. The next few lines are just the opposite, the dumb lambda is executed on the first invocation of foo, and the write and close functions are called on the 2nd invocation of foo. write and close are two lambdas I have also defined as optional parameters. Each take a file stream and invoke the .write and .close methods on it. I do this because the function pointer/dumb lambda selection trick will not work with object methods.

Our function is nothing more than a bunch of nested lists. It can all go on one line now. And better yet, since this is one simple line, it can go on the same line as the def statement...

def foo(a, counter=0,con=None, close=lambda x:x.close(),write=lambda x,s:x.write(s),n=lambda a=0,b=0,c=0:0):((foo,n)[counter](a, 1, (open,n)[counter](a + '.txt''wt')),((n,write)[counter](con, str(a)),(n,close)[counter](con)))

You are visitor #006414
Get notified about new posts by following the newfangled twitter account.
Best viewed with
© 1999 Nerd Paradise