“You can almost never go wrong keeping things as simple as possible.” —David Beazley
Here’s a complete Flask app:
from flask import Flask, request app = Flask(__name__) @app.route('/') def hi(): return "hello, " + request.args.get('name', 'friend') app.run()
On the first line, we import a
This will be familiar
if you’ve written a web app in the past 15 years.
request contains the current HTTP request, the one you’re responding to.
Form values, headers, uploads, cookies, all that stuff.
request is secretly a squid-headed monster
Yet there is something funny about
Flask doesn’t provide it as an argument to
Instead it’s something you import—into
many Python modules, if you like—and that object,
the same object, always contains information for the current request.
Think that over for a second. Many requests, one
How do you think this works with threads?
What if there’s more than one request being handled at a time?
What is the value of
The answer is that
request itself is not really a Request object
but rather something special called a proxy.
Any time you do anything to
such as getting
request proxy forwards that operation to the real Request object
for the current request on the current thread.
How to build your own squid-headed monster
The simplest possible proxy is like this:
thing = "hi" class ThingProxy(object): def __getattribute__(self, name): return getattr(thing, name) proxy = ThingProxy()
If we paste this into Python, we can do things like:
>>> proxy.upper() 'HI'
Methods we call on
proxy are forwarded to
by way of the
__getattribute__ magic method.
If we change
thing, of course
proxy’s behavior changes too:
>>> thing = [1, 2, 3] >>> proxy.pop() 3
However, you can still tell that
proxy is a proxy:
>>> proxy <__main__.ThingProxy object at 0x10a0bd790>
If we wanted to make our
proxy look and feel even more like the real
we can just add a few more magic methods.
class ThingProxy(object): def __getattribute__(self, name): return getattr(thing, name) def __repr__(self): # the magic method for displaying the object return repr(thing) def __add__(self, other): # the magic method for addition return thing + other proxy = ThingProxy() >>> thing = "hi" >>> proxy 'hi' >>> proxy + " everybody" 'hi everybody'
As you can see if you
look at the source,
LocalProxy objects work just the same way.
Of course instead of referring to a single global variable
LocalProxy consults a data structure (called a
that stores one current request per thread.
Flask keeps the
LocalStack up to date as requests come in;
request always delegates all operations
to the current request object for the current thread.
The other difference is that
LocalProxy implements far more
of Python’s magic methods—about 50 in all.
Squid-headed monsters can be bad
Proxies are interesting, and you can do some amazing things with them. But most of the time they’re a bad idea.
They make it harder to understand your code, for several reasons.
First of all, they’re mostly invisible.
The whole point of a proxy is to look and behave just like the target object,
except when you want it to do something different.
Like how the
request proxy is supposed to behave exactly like a Request…
until the next request comes along.
This is inherently a somewhat schizophrenic thing for an object to be doing.
When something goes wrong,
if a proxy is involved, very often you don’t know it at first.
And it can be very frustrating trying to figure out what’s happening.
Even basic debugging functionality
print obj or
will lie to you, betraying no hint that the proxy is there!
Second, when you’re reading source code, proxies are a brick wall.
For example, here’s
the line of code in Flask
request = LocalProxy(partial(_lookup_req_object, 'request'))
It’s not exactly obvious what is going on here. It takes some serious detective work to figure out what kind of object is being proxied.
Third, proxies can change in seemingly impossible ways.
In the example above, we had
proxy change from a str to a list.
Real strings, of course, can’t actually do that.
In Flask, if you set aside a reference to the current request,
in the hopes of looking at it again later,
you may then find that it has mysteriously morphed into a different request,
or it has magically become
Next week I think I’ll try some simple text processing with NLTK. See you then.
comments powered by Disqus