-
-
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. |
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
@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.
@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
waslmb2func
, 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 useflask.views.View.as_view
and the Part 4 technique to get the multiple views required to make the URL shortener work in 5b).