Repository: falconry/falcon

Issue:
Using env.get instead of if...in
I had a question about the following piece of code in falcon.request.Request:

``` python
# PERF(kgriffs): if...in is faster than using env.get(...)
        if 'QUERY_STRING' in env:
            self.query_string = env['QUERY_STRING']

            if self.query_string:
                self._params = parse_query_string(
                    self.query_string,
                    keep_blank_qs_values=self.options.keep_blank_qs_values,
                )

            else:
                self._params = {}

        else:
            self.query_string = ''
            self._params = {}
```

For the part,

``` python
 if 'QUERY_STRING' in env:
     self.query_string = env['QUERY_STRING']
```

Arent you performing two hash operations (one for checking and one for retrieving)? Based on that, env.get should be better? 



Discussion:
If the key exists in most circumstance, the code below is faster than both `.get()` and `if ... in ...`:

``` python
try:
    self.query_string = env['QUERY_STRING']
except KeyError:
    self.query_string = ''
    self._params = {}
else:
    if self.query_string:
        self._params = parse_query_string(
            self.query_string,
            keep_blank_qs_values=self.options.keep_blank_qs_values,
        )

    else:
        self._params = {}
```

Arguably more Pythonic, too (EAFP and all that.)

On Wed, Oct 21, 2015 at 12:16 AM, Philip Tzou notifications@github.com
wrote:

> This's faster than both .get() and if ... in ...:
> 
> try:
>     self.query_string = env['QUERY_STRING']except KeyError:
>     pass
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/falconry/falcon/issues/637#issuecomment-149777120.

## 

.

I agree that is more Pythonic but the query string does not always exist so based on that it could be slower because catching the exception is the expensive part. 

try/catch is actually cheaper in python than people seem to assume, see,
inter alia,
http://stackoverflow.com/questions/2522005/cost-of-exception-handlers-in-python

On Wed, Oct 21, 2015 at 11:15 AM, Usman Ehtesham Gul <
notifications@github.com> wrote:

> I agree that is more Pythonic but the query string does not always exist
> so based on that it could be slower because catching the exception is the
> expensive part.
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/falconry/falcon/issues/637#issuecomment-149928413.

## 

.

Lol...that was the exact link i was referring to where if you see below in the same link:
"A try/except block is extremely efficient if no exceptions are raised Actually catching an exception is expensive."

@ueg1990 `QUERY_STRING` is [one of the standard WSGI `envrion` keys](http://wsgi.readthedocs.org/en/latest/definitions.html#envvar-QUERY_STRING). I consider it's a strong signal that `QUERY_STRING` exists in most circumstance. I also did a small investigate of some popular HTTP servers which are compatible with WSGI and below is my result:
- In Gunicorn, `QUERY_STRING` is one of the [default environs](https://github.com/benoitc/gunicorn/blob/master/gunicorn/http/wsgi.py#L89)
- In CherryPy, `QUERY_STRING` is defined in [`get_environ()` method](https://bitbucket.org/cherrypy/cherrypy/src/252fccc270d44650ef7e8d3d4b54ea10ae83b051/cherrypy/wsgiserver/wsgiserver2.py#wsgiserver2.py-2369)
- In Tornado, `QUERY_STRING` is one of the defaults in [`environ()` method](https://github.com/tornadoweb/tornado/blob/stable/tornado/wsgi.py#L324)
- Even nginx's [uwsgi_params file](https://github.com/nginx/nginx/blob/master/conf/uwsgi_params#L2) defines `QUERY_STRING`

thanks for sharing...that does help alot :)

Based on @philiptzou's observations, it seems like moving this to a try..catch would make sense.

I will be working on this :) 


Resolving patch:
diff --git a/falcon/request.py b/falcon/request.py
--- a/falcon/request.py
+++ b/falcon/request.py
@@ -272,10 +272,13 @@ def __init__(self, env, options=None):
         else:
             self.path = '/'
 
-        # PERF(kgriffs): if...in is faster than using env.get(...)
-        if 'QUERY_STRING' in env:
+        # PERF(ueg1990): try/catch cheaper and faster (and more Pythonic)
+        try:
             self.query_string = env['QUERY_STRING']
-
+        except KeyError:
+            self.query_string = ''
+            self._params = {}
+        else:
             if self.query_string:
                 self._params = parse_query_string(
                     self.query_string,
@@ -285,10 +288,6 @@ def __init__(self, env, options=None):
             else:
                 self._params = {}
 
-        else:
-            self.query_string = ''
-            self._params = {}
-
         self._cookies = None
 
         self._cached_headers = None


Test patch: