-
-
Save MineRobber9000/19c331a9f5d8e994a4ed251f0ffa1e98 to your computer and use it in GitHub Desktop.
########################################################### | |
# 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. |
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! 😆
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
Import? Format? Operators? No, thanks. Real gangsta use ONLY lambdas:
https://github.com/orsinium-labs/python-lambda-calculus
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")
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
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).
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.
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')]
Interesting stuff.
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
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
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!