Unified version checks where appropriate
diff --git a/jinja2/_compat.py b/jinja2/_compat.py
index f10ef65..13b1382 100644
--- a/jinja2/_compat.py
+++ b/jinja2/_compat.py
@@ -30,6 +30,8 @@
 string_types = six.string_types
 
 iteritems = six.iteritems
+iterkeys = six.iterkeys
+itervalues = six.itervalues
 
 if six.PY3:
     from io import BytesIO, StringIO
diff --git a/jinja2/bccache.py b/jinja2/bccache.py
index dbfc3c2..a3bb002 100644
--- a/jinja2/bccache.py
+++ b/jinja2/bccache.py
@@ -21,11 +21,11 @@
 import fnmatch
 from hashlib import sha1
 from jinja2.utils import open_if_exists
-from jinja2._compat import BytesIO, pickle
+from jinja2._compat import BytesIO, pickle, PY3
 
 
 # marshal works better on 3.x, one hack less required
-if sys.version_info[0] >= 3:
+if PY3:
     marshal_dump = marshal.dump
     marshal_load = marshal.load
 else:
diff --git a/jinja2/exceptions.py b/jinja2/exceptions.py
index 0075be0..b137353 100644
--- a/jinja2/exceptions.py
+++ b/jinja2/exceptions.py
@@ -8,14 +8,13 @@
     :copyright: (c) 2010 by the Jinja Team.
     :license: BSD, see LICENSE for more details.
 """
-import sys
 from jinja2._compat import imap, text_type, PY3
 
 
 class TemplateError(Exception):
     """Baseclass for all template errors."""
 
-    if sys.version_info[0] < 3:
+    if not PY3:
         def __init__(self, message=None):
             if message is not None:
                 message = text_type(message).encode('utf-8')
diff --git a/jinja2/testsuite/__init__.py b/jinja2/testsuite/__init__.py
index 335292d..95166f5 100644
--- a/jinja2/testsuite/__init__.py
+++ b/jinja2/testsuite/__init__.py
@@ -16,6 +16,7 @@
 import unittest
 from traceback import format_exception
 from jinja2 import loaders
+from jinja2._compat import PY3
 
 
 here = os.path.dirname(os.path.abspath(__file__))
@@ -89,7 +90,7 @@
 
     # doctests will not run on python 3 currently.  Too many issues
     # with that, do not test that on that platform.
-    if sys.version_info[0] < 3:
+    if not PY3:
         suite.addTest(doctests.suite())
 
     return suite
diff --git a/jinja2/testsuite/ext.py b/jinja2/testsuite/ext.py
index 9fd75ea..0f93be9 100644
--- a/jinja2/testsuite/ext.py
+++ b/jinja2/testsuite/ext.py
@@ -9,7 +9,6 @@
     :license: BSD, see LICENSE for more details.
 """
 import re
-import six
 import unittest
 
 from jinja2.testsuite import JinjaTestCase
@@ -18,8 +17,7 @@
 from jinja2.exceptions import TemplateAssertionError
 from jinja2.ext import Extension
 from jinja2.lexer import Token, count_newlines
-from jinja2._compat import next
-from six import BytesIO
+from jinja2._compat import next, BytesIO, itervalues, text_type
 
 importable_object = 23
 
@@ -219,7 +217,7 @@
         original = Environment(extensions=[TestExtension])
         overlay = original.overlay()
         for env in original, overlay:
-            for ext in six.itervalues(env.extensions):
+            for ext in itervalues(env.extensions):
                 assert ext.environment is env
 
     def test_preprocessor_extension(self):
@@ -438,7 +436,7 @@
         '''
         tmpl = env.from_string(tmplsource)
         assert tmpl.render(val=True).split()[0] == 'Markup'
-        assert tmpl.render(val=False).split()[0] == six.text_type.__name__
+        assert tmpl.render(val=False).split()[0] == text_type.__name__
 
         # looking at the source we should see <testing> there in raw
         # (and then escaped as well)
diff --git a/jinja2/testsuite/filters.py b/jinja2/testsuite/filters.py
index 88f93f9..31c9559 100644
--- a/jinja2/testsuite/filters.py
+++ b/jinja2/testsuite/filters.py
@@ -12,8 +12,7 @@
 from jinja2.testsuite import JinjaTestCase
 
 from jinja2 import Markup, Environment
-import six
-from six.moves import map
+from jinja2._compat import text_type, PY3
 
 env = Environment()
 
@@ -190,7 +189,7 @@
     def test_string(self):
         x = [1, 2, 3, 4, 5]
         tmpl = env.from_string('''{{ obj|string }}''')
-        assert tmpl.render(obj=x) == six.text_type(x)
+        assert tmpl.render(obj=x) == text_type(x)
 
     def test_title(self):
         tmpl = env.from_string('''{{ "foo bar"|title }}''')
@@ -299,8 +298,13 @@
             def __init__(self, value):
                 self.value = value
             def __unicode__(self):
-                return six.text_type(self.value)
-            __str__ = __unicode__
+                return text_type(self.value)
+            if PY3:
+                __str__ = __unicode__
+                del __unicode__
+            else:
+                def __str__(self):
+                    return self.__unicode__().encode('utf-8')
         tmpl = env.from_string('''{{ items|sort(attribute='value')|join }}''')
         assert tmpl.render(items=map(Magic, [3, 2, 4, 1])) == '1234'
 
diff --git a/jinja2/testsuite/lexnparse.py b/jinja2/testsuite/lexnparse.py
index cbe0f2c..e266111 100644
--- a/jinja2/testsuite/lexnparse.py
+++ b/jinja2/testsuite/lexnparse.py
@@ -8,22 +8,20 @@
     :copyright: (c) 2010 by the Jinja Team.
     :license: BSD, see LICENSE for more details.
 """
-import sys
-import six
 import unittest
 
 from jinja2.testsuite import JinjaTestCase
 
 from jinja2 import Environment, Template, TemplateSyntaxError, \
      UndefinedError, nodes
-from jinja2._compat import next
+from jinja2._compat import next, iteritems, text_type, PY3
 from jinja2.lexer import Token, TokenStream, TOKEN_EOF, TOKEN_BLOCK_BEGIN, TOKEN_BLOCK_END
 
 env = Environment()
 
 
 # how does a string look like in jinja syntax?
-if sys.version_info[0] < 3:
+if not PY3:
     def jinja_string_repr(string):
         return repr(string)[1:]
 else:
@@ -95,7 +93,7 @@
 
     def test_operators(self):
         from jinja2.lexer import operators
-        for test, expect in six.iteritems(operators):
+        for test, expect in iteritems(operators):
             if test in '([{}])':
                 continue
             stream = env.lexer.tokenize('{{ %s }}' % test)
@@ -377,7 +375,7 @@
     def test_notin(self):
         bar = range(100)
         tmpl = env.from_string('''{{ not 42 in bar }}''')
-        assert tmpl.render(bar=bar) == six.text_type(not 42 in bar)
+        assert tmpl.render(bar=bar) == text_type(not 42 in bar)
 
     def test_implicit_subscribed_tuple(self):
         class Foo(object):
diff --git a/jinja2/testsuite/regression.py b/jinja2/testsuite/regression.py
index a4e6631..c5f7d5c 100644
--- a/jinja2/testsuite/regression.py
+++ b/jinja2/testsuite/regression.py
@@ -14,7 +14,7 @@
 
 from jinja2 import Template, Environment, DictLoader, TemplateSyntaxError, \
      TemplateNotFound, PrefixLoader
-import six
+from jinja2._compat import text_type
 
 env = Environment()
 
@@ -119,7 +119,7 @@
 
         ''')
 
-        assert tmpl.render().split() == [six.text_type(x) for x in range(1, 11)] * 5
+        assert tmpl.render().split() == [text_type(x) for x in range(1, 11)] * 5
 
     def test_weird_inline_comment(self):
         env = Environment(line_statement_prefix='%')
diff --git a/jinja2/testsuite/security.py b/jinja2/testsuite/security.py
index c892fed..246d0f0 100644
--- a/jinja2/testsuite/security.py
+++ b/jinja2/testsuite/security.py
@@ -18,7 +18,7 @@
 from jinja2 import Markup, escape
 from jinja2.exceptions import SecurityError, TemplateSyntaxError, \
      TemplateRuntimeError
-import six
+from jinja2._compat import text_type
 
 
 class PrivateStuff(object):
@@ -77,7 +77,7 @@
         # adding two strings should escape the unsafe one
         unsafe = '<script type="application/x-some-script">alert("foo");</script>'
         safe = Markup('<em>username</em>')
-        assert unsafe + safe == six.text_type(escape(unsafe)) + six.text_type(safe)
+        assert unsafe + safe == text_type(escape(unsafe)) + text_type(safe)
 
         # string interpolations are safe to use too
         assert Markup('<em>%s</em>') % '<bad user>' == \
@@ -115,7 +115,7 @@
                             '{{ say_hello("<blink>foo</blink>") }}')
         escaped_out = '<p>Hello &lt;blink&gt;foo&lt;/blink&gt;!</p>'
         assert t.render() == escaped_out
-        assert six.text_type(t.module) == escaped_out
+        assert text_type(t.module) == escaped_out
         assert escape(t.module) == escaped_out
         assert t.module.say_hello('<blink>foo</blink>') == escaped_out
         assert escape(t.module.say_hello('<blink>foo</blink>')) == escaped_out
diff --git a/jinja2/utils.py b/jinja2/utils.py
index f3cf10e..d499d49 100644
--- a/jinja2/utils.py
+++ b/jinja2/utils.py
@@ -23,7 +23,7 @@
     except ImportError:
         from dummy_thread import allocate_lock
 from collections import deque
-from jinja2._compat import text_type, string_types, Iterator
+from jinja2._compat import text_type, string_types, Iterator, PY3
 
 
 _word_split_re = re.compile(r'(\s+)')
@@ -53,7 +53,7 @@
 # This is used in a couple of places.  As far as Jinja is concerned
 # filenames are unicode *or* bytestrings in 2.x and unicode only in
 # 3.x because compile cannot handle bytes
-if sys.version_info[0] < 3:
+if not PY3:
     def _encode_filename(filename):
         if isinstance(filename, unicode):
             return filename.encode('utf-8')