Skip to content

Instantly share code, notes, and snippets.

@MineRobber9000
Last active February 8, 2024 12:48
Show Gist options
  • Save MineRobber9000/19c331a9f5d8e994a4ed251f0ffa1e98 to your computer and use it in GitHub Desktop.
Save MineRobber9000/19c331a9f5d8e994a4ed251f0ffa1e98 to your computer and use it in GitHub Desktop.
How to NEVER use lambdas - Python 3 edition
###########################################################
# How to NEVER use lambdas. An inneficient and yet educa- #
# tonal [sic] guide to the proper misuse of the lambda #
# construct in Python 3.x. [DO NOT USE ANY OF THIS EVER] #
# original by (and apologies to): e000 (13/6/11) #
# now in Python 3 courtesy of: khuxkm (17/9/20) #
###########################################################
## Part 1. Basic LAMBDA Introduction ##
# If you're reading this, you've probably already read e000's
# original, but in case you didn't read that one back when it
# was big, I should explain what lambdas are. As per e000:
# (quote)
# Lambdas are pretty much anonymous "one line" functions
# that are able to be constructed at runtime. Typical usage
# would be to plug it into `filter()` or `map()`, but that's
# boring. Let's start with the most basic of basic examples.
# (endquote)
def pow(x, power):
return x**power
# Simple enough, just a function that raises `x` to the `power`
# power. Now let's do it in lambda form:
pow = lambda x, power: x**power
# Easy.
## Part 2. Scoping within Lambdas ##
# Again, this part should be familiar to you if you read the
# original by e000. Nothing changed in Python 3, so I'll just
# quote the explanation here: (quote)
# Let's try something a bit more complicated. A random
# string or password generator? Sure.
import random, string
characters = string.digits + string.letters
def randomPasswordGenerator(length):
return ''.join(random.choice(characters) for i in range(length))
# >>> randomPasswordGenerator(8)
# 'bDpHVxiO'
# Haah! That was cake! Now in this terrible tutorial, we're going to
# prohibit the use of defining ANYTHING outside of the lambda function,
# including any kind of variable, or import. So, how are we going to get
# `random` and `string`. Well the answer is obvious, we're going to make
# a lambda inside of a lambda inside of a lambda. We're also going to use
# a bit of `__import__` trickery.
# (endquote)
# The import trickery remains the same. Like I said, nothing really
# changed in Python 3 to break this example. As such, I even copied the
# source directly from donotuse.py (changing `xrange` to `range` as the
# former no longer exists).
randomPasswordGenerator = \
(lambda random, string: # level 1
(lambda characters: # level 2
lambda length: ''.join(random.choice(characters) for i in range(length)) # level 3
)(string.digits + string.letters) # level 2 args
)(
__import__('random'), # level 1 args
__import__('string')
)
# That's... unpythonic, disgusting, an abomination, and some might even
# call it ungodly. Why would anyone do that to themselves?
# One word: masochists.
## Part 3. Giving lambdas function names ##
# This is the first semi-new part. I'll quote e000 until there's new info.
# (quote)
# In a world where absurdity peaks, and somehow we NEED a
# function name, for what ever reason. Here's how to do it.
# THIS IS NOT FOR THE WEAK HEARTED.
# First, let's start with some regular code.
def myFunc(a, b):
return a + b
# >>> myFunc
# <function myFunc at 0x...>
myLambda = lambda a, b: a + b
# >>> myLambda
# <function <lambda> at 0x...>
# Uh'oh! How are we going to give this function a name?
# (endquote)
# In Python 2, we could use `new.function`. But in Python 3, `new` was
# replaced with `types`. Somehow, the new way to do it is even worse.
myFunc = (lambda types:
types.FunctionType((lambda a, b: a + b).__code__.replace(co_name="myFunc"),{},"myFunc")
)(__import__("types"))
# >>> myFunc
# <function myFunc at 0x...>
# In the immortal words of e000, "LOL! It works! Isn't that disgusting?"
## Part 4. A class? In my lambda? It's more likely than you may think. ##
# Let's start with a simple class. I'll write my own example this time. How
# about a simple namespace?
class Namespace:
def __init__(self,**kwargs):
self.__dict__.update(kwargs)
def __repr__(self):
keys = sorted(self.__dict__)
items = ("{}={!r}".format(k,self.__dict__[k]) for k in keys)
return "{}({})".format(type(self).__name__,", ".join(items))
def __eq__(self,other):
return other.__dict__==self.__dict__
# Yes, I know that's basically just types.SimpleNamespace, but shush. Let's
# lambda-ify it. Instead of `new.classobj`, we have `types.new_class`.
Namespace = (lambda types:
types.new_class("Namespace",(),exec_body=(lambda ns: ns.update(
dict(
__init__=(lambda self,**kwargs: self.__dict__.update(kwargs)),
__repr__=(lambda self: "{}({})".format(type(self).__name__,", ".join("{}={!r}".format(k,self.__dict__[k]) for k in sorted(self.__dict__)))),
__eq__=(lambda self, other: self.__dict__==other.__dict__)
)
)))
)(__import__("types"))
# >>> Namespace(x=3,s="Hello world!")
# Namespace(s='Hello world!', x=3)
# Holy mother of pearl, that is an abomination.
## Part 5. Flask + Lambdas = Even More of An Abomination ##
# This is as far as I'll go (mainly because I don't know how to Twisted).
# If you want to go even deeper, use the dark arts I've already taught you
# to convert Parts 6a and 6b into Python 3.
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return "Hello, world!"
app.run()
# And that becomes...
(lambda flask:
(lambda app:
(app,
app.route('/')(lambda: 'Hello, world!')
)[0]
)(flask.Flask(__name__)).run()
)(__import__("flask"))
# What a disaster.
## Part 5b. I Lied, This Is Worse ##
# No comment.
from flask import Flask, redirect
shortnames = {"google":"https://google.com/","khuxkm":"https://khuxkm.tilde.team","*":"https://example.com"}
app = Flask(__name__)
@app.route('/')
def index():
return redirect(shortnames.get("default","https://example.com"),code=307)
@app.route('/<path:short>')
def route(short):
return redirect(shortnames.get(short,shortnames.get("default","https://example.com")),code=307)
app.run()
# Pulling out all of the stops here...
(lambda flask, flaskviews, types, shortnames:
(lambda lmb2view:
(lambda app, index, route:
(app,
app.route("/")(index),
app.route("/<path:short>")(route)
)[0]
)(flask.Flask(__name__),
lmb2view(lambda s: flask.redirect(shortnames.get("default","https://example.com"),code=307),"index"),
lmb2view(lambda s,short: flask.redirect(shortnames.get(short,shortnames.get("default","https://example.com")),code=307),"route")).run()
)(lambda lmb,name: types.new_class(name,(flaskviews.views.View,),{},(lambda ns: ns.update(dict(dispatch_request=lmb)))).as_view(name))
)(__import__("flask"),__import__("flask.views"),__import__("types"),
{
"google":"https://google.com/",
"khuxkm":"https://khuxkm.tilde.team",
"*":"https://example.com"
})
# What? Just... what? It's so goddamn big it barely fits on my 151-column monitor, it breaks all
# sorts of Zen, and I should probably be executed for crimes against humanity, but it's a URL
# shortener implemented entirely in lambdas.
# You ever write completely morally sound code that still leaves you feeling dirty afterwards?
# Me too.
@Hultner
Copy link

Hultner commented Sep 18, 2020

Fun examples, it's always nice to play around with code in creative ways that doesn't make sense.

What would elevate this to a truly educating piece would be a postface discussing some proper techniques to use lambdas in python, showcase some of the few rare places where they do make sense.

Thanks for the fun writeup!

@sobolevn
Copy link

You can also replace lambda definition with _: https://github.com/dry-python/lambdas

So, for example:

>>> scores = [
...     {'name': 'Nikita', 'score': 2},
...     {'name': 'Oleg', 'score': 1},
...     {'name': 'Pavel', 'score': 4},
... ]

>>> print(sorted(scores, key=lambda item: item['score']))
[{'name': 'Oleg', 'score': 1}, {'name': 'Nikita', 'score': 2}, {'name': 'Pavel', 'score': 4}]

The last part can be written as:

>>> print(sorted(scores, key=_['score']))
[{'name': 'Oleg', 'score': 1}, {'name': 'Nikita', 'score': 2}, {'name': 'Pavel', 'score': 4}]

Imagine the possibilities! 😆

@lubieowoce
Copy link

lubieowoce commented Sep 18, 2020

i also recommend abusing list comprehensions for name binding

# Part 2'

randomPasswordGenerator = lambda length: [
    ''.join(random.choice(characters) for i in range(length))
    for (random, string) in [(__import__('random'), __import__('string'))]
    for characters in [string.digits + string.ascii_letters]
][0]

reads kind of like a haskell where-clause :)

edit: apparently string.letters is gone in python 3, replaced with string.ascii_letters

@orsinium
Copy link

Import? Format? Operators? No, thanks. Real gangsta use ONLY lambdas:
https://github.com/orsinium-labs/python-lambda-calculus

@lubieowoce
Copy link

lubieowoce commented Sep 18, 2020

i forgot about default arguments! you can use them to make lambda-fied code less inside-out:

randomPasswordGenerator = lambda length: \
    (lambda random = __import__('random'), string = __import__('string'):
    (lambda characters = string.digits + string.ascii_letters:
        ''.join(random.choice(characters) for i in range(length))
    )()
    )()

oh and you could hoist the import outside the function so that it only gets imported once:

randomPasswordGenerator = \
    (lambda random = __import__('random'), string = __import__('string'):
    (lambda characters = string.digits + string.ascii_letters:
    
    (lambda length:
        ''.join(random.choice(characters) for i in range(length))
    )
    
    )()
    )()

btw it's kinda funny that before JS got modules, lambda-wrapping was commonly used to emulate namespaces/modules (google "IIFE")

@A5rocks
Copy link

A5rocks commented Sep 18, 2020

Even funner is when you use asyncio.coroutine to wrap lambdas and get async functions - I'm sure there's a way to do this involving a generator made in a lambda but I'm too scared to figure it out. (And cool trick using tuples to evaluate multiple things at the same time instead of nested lambdas by the way)

Example that probably isn't so great but oh well: https://github.com/A5rocks/april-fools-bot/blob/master/bot.py

@MineRobber9000
Copy link
Author

Fun examples, it's always nice to play around with code in creative ways that doesn't make sense.

What would elevate this to a truly educating piece would be a postface discussing some proper techniques to use lambdas in python, showcase some of the few rare places where they do make sense.

Thanks for the fun writeup!

@Hultner I totally agree on that first point. The URL shortener in 5b, in particular, was really fun to mess with (when I first got it working I was absolutely shocked). Perhaps, at a later date, I'll come back and maybe write a "How to ACTUALLY use lambdas" in the same style as this one and the original.

i forgot about default arguments! you can use them to make lambda-fied code less inside-out:
-snip-
oh and you could hoist the import outside the function so that it only gets imported once:
-snip-

@lubieowoce but where's the fun in that? part of the fun for me is to see just how "inside-out" you can make it, plus being able to figure out solving the problem (for instance: originally lmb2view was lmb2func, and bound the lambda's code to a function name (using the Part 3 technique), but that broke for some reason, so I got to use flask.views.View.as_view and the Part 4 technique to get the multiple views required to make the URL shortener work in 5b).

@c99pedant
Copy link

Here's a link to e000's gist you refer to: https://gist.github.com/e000/1023982 in case anyone is unlazy enough to read it yet too lazy to not search for the link. Some people could still be using python2.

@lubieowoce
Copy link

lubieowoce commented Sep 22, 2020

@A5rocks

I'm sure there's a way to do this involving a generator made in a lambda but I'm too scared to figure it out.

i was bored and figured out iterator-ish generators! iterator-ish because they're based on generator-expressions, so they can't support .send(...). it's... a lot

"""
# the goal

def upto(n):
  "equivalent to range(n)"
  i = 0
  while i < n:
    yield i
    i += 1


# replacing loop with recursion

def upto(n):
  def go(i):
    if not (i < n):
      return
    yield i
    yield from go(i+1)
  return go(0)
""""

# the lambdafied version

# fix-point combinator, aka "the Y combinator"
# used to define recursive lambdas

fix = lambda f: f(lambda *args, **kwargs: fix(f)(*args, **kwargs))


upto = lambda n: (
  (lambda go = to_gen_func(fix(lambda _go:
    lambda i:
      (i, lambda: _go(i+1)) # yield i and continuation
      if i < n else
      (None, None) # end
  )):
    go(0)
  )()
)

# `go(i)` returns a tuple - the yielded value and a "continuation" `cont`,
# i.e. something we can call to "continue" the generator.
# `cont()` will return another yielded value and another continuation, etc;
# until finally, the returned continuation is `None` - then we know the generator is done.
# to_gen_func converts a "coroutine" like the above to a native python coroutine.

"""
# to_gen_func - "normal" implementation

def to_gen_func(f):
  def f2(*args, **kwargs):
    step = lambda: f(*args, **kwargs)
    while True:
      (yielded_value, step2) = step()
      if step2 == None:
        return
      yield yielded_value
      step = step2
  
  return f2
"""

# to_gen_func - lambdafied implementation

# with some generator expressions, mutable state and exceptions
# everything is possible.

forever = type('forever', (), {
  '__doc__': 'infinite iterator',
  '__iter__': lambda s: s,
  '__next__': lambda s: None,
})


# will be needed to break the above

throw_StopIteration = lambda: next(iter([]))

# okay, here we go...

to_gen_func = lambda f: (lambda *args, **kwargs:
  (lambda state = {'step': lambda: f(*args, **kwargs)}:
     # generator expression that does all the work
     (yielded_val
       for _ in forever()
       for (yielded_val, step2) in [state['step']()]
       for _ in [
         throw_StopIteration() if step2 == None
         else state.update({'step': step2})
       ]
     )
  )()
) 

# tests

assert list(zip(upto(10000000), 'abc')) == [(0, 'a'), (1, 'b'), (2, 'c')]



# infinite generator: list(countup()) = [0, 1, 2, 3, ...]

countup = lambda: (
  (lambda go = to_gen_func(fix(lambda _go:
    lambda i: (i, lambda: _go(i+1))
  )):
    go(0)
  )()
)

assert list(zip(countup(), 'abc')) == [(0, 'a'), (1, 'b'), (2, 'c')]

@alexmojaki
Copy link

@rautamiekka
Copy link

Interesting stuff.

@terrdavis
Copy link

Don't for get assignment expressions!

randomPasswordGenerator = lambda length: (
    (random:=__import('random'))
    and (string:=__import__('string'))
    and (characters:=string.digits + string.ascii_letters)
    and ''.join(map(random.choice, [characters] * length))
)

Adapted from my answer here:
https://stackoverflow.com/a/56976939/9560908

@rautamiekka
Copy link

Don't for get assignment expressions!

randomPasswordGenerator = lambda length: (
    (random:=__import('random'))
    and (string:=__import__('string'))
    and (characters:=string.digits + string.ascii_letters)
    and ''.join(map(random.choice, [characters] * length))
)

Adapted from my answer here:
https://stackoverflow.com/a/56976939/9560908

That's kinda clever.

Pretty sure the devs didn't think of that :P

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment