From 5664338e22c1312e68293181a41b290434b2018a Mon Sep 17 00:00:00 2001 From: Stefan Kjartansson Date: Tue, 10 Jul 2012 15:27:50 +0000 Subject: typo in "django/docs/topics/python3.txt" --- docs/topics/python3.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/topics/python3.txt') diff --git a/docs/topics/python3.txt b/docs/topics/python3.txt index 974ddb0e88..1aea252e3f 100644 --- a/docs/topics/python3.txt +++ b/docs/topics/python3.txt @@ -181,7 +181,7 @@ xrange range xrange =============================== ====================================== ====================== -Ouptut encoding now Unicode +Output encoding now Unicode =========================== If you want to catch stdout/stderr output, the output content is UTF-8 encoded -- cgit v1.3 From 8b0190984116873158ee627c7a033a7bd4c3a199 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 20 Jul 2012 11:32:38 +0200 Subject: [py3] Bundled six for Python 3 compatibility. Refs #18363. --- django/utils/py3.py | 109 --------------- django/utils/six.py | 353 ++++++++++++++++++++++++++++++++++++++++++++++++ docs/topics/python3.txt | 253 ++++------------------------------ 3 files changed, 378 insertions(+), 337 deletions(-) delete mode 100644 django/utils/py3.py create mode 100644 django/utils/six.py (limited to 'docs/topics/python3.txt') diff --git a/django/utils/py3.py b/django/utils/py3.py deleted file mode 100644 index 8a5c37e976..0000000000 --- a/django/utils/py3.py +++ /dev/null @@ -1,109 +0,0 @@ -# Compatibility layer for running Django both in 2.x and 3.x - -import sys - -if sys.version_info[0] < 3: - PY3 = False - # Changed module locations - from urlparse import (urlparse, urlunparse, urljoin, urlsplit, urlunsplit, - urldefrag, parse_qsl) - from urllib import (quote, unquote, quote_plus, urlopen, urlencode, - url2pathname, urlretrieve, unquote_plus) - from urllib2 import (Request, OpenerDirector, UnknownHandler, HTTPHandler, - HTTPSHandler, HTTPDefaultErrorHandler, FTPHandler, - HTTPError, HTTPErrorProcessor) - import urllib2 - import Cookie as cookies - try: - import cPickle as pickle - except ImportError: - import pickle - try: - import thread - except ImportError: - import dummy_thread as thread - from htmlentitydefs import name2codepoint - import HTMLParser - from os import getcwdu - from itertools import izip as zip - unichr = unichr - xrange = xrange - maxsize = sys.maxint - - # Type aliases - string_types = basestring, - text_type = unicode - integer_types = int, long - long_type = long - - from io import BytesIO as OutputIO - - # Glue code for syntax differences - def reraise(tp, value, tb=None): - exec("raise tp, value, tb") - - def with_metaclass(meta, base=object): - class _DjangoBase(base): - __metaclass__ = meta - return _DjangoBase - - iteritems = lambda o: o.iteritems() - itervalues = lambda o: o.itervalues() - iterkeys = lambda o: o.iterkeys() - - # n() is useful when python3 needs a str (unicode), and python2 str (bytes) - n = lambda s: s.encode('utf-8') - -else: - PY3 = True - import builtins - - # Changed module locations - from urllib.parse import (urlparse, urlunparse, urlencode, urljoin, - urlsplit, urlunsplit, quote, unquote, - quote_plus, unquote_plus, parse_qsl, - urldefrag) - from urllib.request import (urlopen, url2pathname, Request, OpenerDirector, - UnknownHandler, HTTPHandler, HTTPSHandler, - HTTPDefaultErrorHandler, FTPHandler, - HTTPError, HTTPErrorProcessor, urlretrieve) - import urllib.request as urllib2 - import http.cookies as cookies - import pickle - try: - import _thread as thread - except ImportError: - import _dummy_thread as thread - from html.entities import name2codepoint - import html.parser as HTMLParser - from os import getcwd as getcwdu - zip = zip - unichr = chr - xrange = range - maxsize = sys.maxsize - - # Type aliases - string_types = str, - text_type = str - integer_types = int, - long_type = int - - from io import StringIO as OutputIO - - # Glue code for syntax differences - def reraise(tp, value, tb=None): - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value - - def with_metaclass(meta, base=object): - ns = dict(base=base, meta=meta) - exec("""class _DjangoBase(base, metaclass=meta): - pass""", ns) - return ns["_DjangoBase"] - - iteritems = lambda o: o.items() - itervalues = lambda o: o.values() - iterkeys = lambda o: o.keys() - - n = lambda s: s diff --git a/django/utils/six.py b/django/utils/six.py new file mode 100644 index 0000000000..6526d76cb1 --- /dev/null +++ b/django/utils/six.py @@ -0,0 +1,353 @@ +"""Utilities for writing code that runs on Python 2 and 3""" + +import operator +import sys +import types + +__author__ = "Benjamin Peterson " +__version__ = "1.1.0" + + +# True if we are running on Python 3. +PY3 = sys.version_info[0] == 3 + +if PY3: + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = basestring, + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + def __len__(self): + return 1 << 31 + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) + # This is a bit ugly, but it avoids running this again. + delattr(tp, self.name) + return result + + +class MovedModule(_LazyDescr): + + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + +class MovedAttribute(_LazyDescr): + + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + + +class _MovedItems(types.ModuleType): + """Lazy loading of moved objects""" + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute("map", "itertools", "builtins", "imap", "map"), + MovedAttribute("reload_module", "__builtin__", "imp", "reload"), + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), + + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule("copyreg", "copy_reg"), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", + "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", + "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", + "tkinter.simpledialog"), + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("winreg", "_winreg"), +] +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) +del attr + +moves = sys.modules["six.moves"] = _MovedItems("moves") + + +def add_move(move): + """Add an item to six.moves.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from six.moves.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_code = "__code__" + _func_defaults = "__defaults__" + + _iterkeys = "keys" + _itervalues = "values" + _iteritems = "items" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_code = "func_code" + _func_defaults = "func_defaults" + + _iterkeys = "iterkeys" + _itervalues = "itervalues" + _iteritems = "iteritems" + + +if PY3: + def get_unbound_function(unbound): + return unbound + + + advance_iterator = next + + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) +else: + def get_unbound_function(unbound): + return unbound.im_func + + + def advance_iterator(it): + return it.next() + + callable = callable +_add_doc(get_unbound_function, + """Get the function out of a possibly unbound function""") + + +get_method_function = operator.attrgetter(_meth_func) +get_method_self = operator.attrgetter(_meth_self) +get_function_code = operator.attrgetter(_func_code) +get_function_defaults = operator.attrgetter(_func_defaults) + + +def iterkeys(d): + """Return an iterator over the keys of a dictionary.""" + return getattr(d, _iterkeys)() + +def itervalues(d): + """Return an iterator over the values of a dictionary.""" + return getattr(d, _itervalues)() + +def iteritems(d): + """Return an iterator over the (key, value) pairs of a dictionary.""" + return getattr(d, _iteritems)() + + +if PY3: + def b(s): + return s.encode("latin-1") + def u(s): + return s + if sys.version_info[1] <= 1: + def int2byte(i): + return bytes((i,)) + else: + # This is about 2x faster than the implementation above on 3.2+ + int2byte = operator.methodcaller("to_bytes", 1, "big") + import io + StringIO = io.StringIO + BytesIO = io.BytesIO +else: + def b(s): + return s + def u(s): + return unicode(s, "unicode_escape") + int2byte = chr + import StringIO + StringIO = BytesIO = StringIO.StringIO +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +if PY3: + import builtins + exec_ = getattr(builtins, "exec") + + + def reraise(tp, value, tb=None): + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + + + print_ = getattr(builtins, "print") + del builtins + +else: + def exec_(code, globs=None, locs=None): + """Execute code in a namespace.""" + if globs is None: + frame = sys._getframe(1) + globs = frame.f_globals + if locs is None: + locs = frame.f_locals + del frame + elif locs is None: + locs = globs + exec("""exec code in globs, locs""") + + + exec_("""def reraise(tp, value, tb=None): + raise tp, value, tb +""") + + + def print_(*args, **kwargs): + """The new-style print function.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + def write(data): + if not isinstance(data, basestring): + data = str(data) + fp.write(data) + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) + +_add_doc(reraise, """Reraise an exception.""") + + +def with_metaclass(meta, base=object): + """Create a base class with a metaclass.""" + return meta("NewBase", (base,), {}) diff --git a/docs/topics/python3.txt b/docs/topics/python3.txt index 1aea252e3f..e4bfc1bd9c 100644 --- a/docs/topics/python3.txt +++ b/docs/topics/python3.txt @@ -2,251 +2,48 @@ Python 3 compatibility ====================== -Django 1.5 introduces a compatibility layer that allows the code to be run both -in Python 2 (2.6/2.7) and Python 3 (>= 3.2) (*work in progress*). +Django 1.5 is the first version of Django to support Python 3. -This document is not meant as a complete Python 2 to Python 3 migration guide. -There are many existing resources you can read. But we describe some utilities -and guidelines that we recommend you should use when you want to ensure your -code can be run with both Python 2 and 3. +The same code runs both on Python 2 (≥2.6.5) and Python 3 (≥3.2). To +achieve this: -* http://docs.python.org/py3k/howto/pyporting.html -* http://python3porting.com/ +- wherever possible, Django uses the six_ compatibility layer, +- all modules declare ``from __future__ import unicode_literals``. -django.utils.py3 -================ +.. _six: http://packages.python.org/six/ + +This document is not meant as a Python 2 to Python 3 migration guide. There +are many existing resources, including `Python's official porting guide`_. But +it describes guidelines that apply to Django's code and are recommended for +pluggable apps that run with both Python 2 and 3. -Whenever a symbol or module has different semantics or different locations on -Python 2 and Python 3, you can import it from ``django.utils.py3`` where it -will be automatically converted depending on your current Python version. +.. _Python's official porting guide: http://docs.python.org/py3k/howto/pyporting.html -PY3 ---- +.. module: django.utils.six -If you need to know anywhere in your code if you are running Python 3 or a -previous Python 2 version, you can check the ``PY3`` boolean variable:: +django.utils.six +================ - from django.utils.py3 import PY3 +Read the documentation of six_. It's the canonical compatibility library for +supporting Python 2 and 3 in a single codebase. - if PY3: - # Do stuff Python 3-wise - else: - # Do stuff Python 2-wise +``six`` is bundled with Django: you can import it as :mod:`django.utils.six`. -This should be considered as a last resort solution when it is not possible -to import a compatible name from django.utils.py3, as described in the sections -below. +.. _string-handling: String handling =============== -In Python 3, all strings are considered Unicode strings by default. Byte strings -have to be prefixed with the letter 'b'. To mimic the same behaviour in Python 2, -we recommend you import ``unicode_literals`` from the ``__future__`` library:: +In Python 3, all strings are considered Unicode strings by default. Byte +strings must be prefixed with the letter ``b``. In order to enable the same +behavior in Python 2, every module must import ``unicode_literals`` from +``__future__``:: from __future__ import unicode_literals my_string = "This is an unicode literal" my_bytestring = b"This is a bytestring" -Be cautious if you have to slice bytestrings. -See http://docs.python.org/py3k/howto/pyporting.html#bytes-literals - -Different expected strings --------------------------- - -Some method parameters have changed the expected string type of a parameter. -For example, ``strftime`` format parameter expects a bytestring on Python 2 but -a normal (Unicode) string on Python 3. For these cases, ``django.utils.py3`` -provides a ``n()`` function which encodes the string parameter only with -Python 2. - - >>> from __future__ import unicode_literals - >>> from datetime import datetime - - >>> print(datetime.date(2012, 5, 21).strftime(n("%m → %Y"))) - 05 → 2012 - -Renamed types -============= - -Several types are named differently in Python 2 and Python 3. In order to keep -compatibility while using those types, import their corresponding aliases from -``django.utils.py3``. - -=========== ========= ===================== -Python 2 Python 3 django.utils.py3 -=========== ========= ===================== -basestring, str, string_types (tuple) -unicode str text_type -int, long int, integer_types (tuple) -long int long_type -=========== ========= ===================== - -String aliases --------------- - -Code sample:: - - if isinstance(foo, basestring): - print("foo is a string") - - # I want to convert a number to a Unicode string - bar = 45 - bar_string = unicode(bar) - -Should be replaced by:: - - from django.utils.py3 import string_types, text_type - - if isinstance(foo, string_types): - print("foo is a string") - - # I want to convert a number to a Unicode string - bar = 45 - bar_string = text_type(bar) - -No more long type ------------------ - -``long`` and ``int`` types have been unified in Python 3, meaning that ``long`` -is no longer available. ``django.utils.py3`` provides both ``long_type`` and -``integer_types`` aliases. For example: - -.. code-block:: python - - # Old Python 2 code - my_var = long(333463247234623) - if isinstance(my_var, (int, long)): - # ... - -Should be replaced by: - -.. code-block:: python - - from django.utils.py3 import long_type, integer_types - - my_var = long_type(333463247234623) - if isinstance(my_var, integer_types): - # ... - - -Changed module locations -======================== - -The following modules have changed their location in Python 3. Therefore, it is -recommended to import them from the ``django.utils.py3`` compatibility layer: - -=============================== ====================================== ====================== -Python 2 Python3 django.utils.py3 -=============================== ====================================== ====================== -Cookie http.cookies cookies - -urlparse.urlparse urllib.parse.urlparse urlparse -urlparse.urlunparse urllib.parse.urlunparse urlunparse -urlparse.urljoin urllib.parse.urljoin urljoin -urlparse.urlsplit urllib.parse.urlsplit urlsplit -urlparse.urlunsplit urllib.parse.urlunsplit urlunsplit -urlparse.urldefrag urllib.parse.urldefrag urldefrag -urlparse.parse_qsl urllib.parse.parse_qsl parse_qsl -urllib.quote urllib.parse.quote quote -urllib.unquote urllib.parse.unquote unquote -urllib.quote_plus urllib.parse.quote_plus quote_plus -urllib.unquote_plus urllib.parse.unquote_plus unquote_plus -urllib.urlencode urllib.parse.urlencode urlencode -urllib.urlopen urllib.request.urlopen urlopen -urllib.url2pathname urllib.request.url2pathname url2pathname -urllib.urlretrieve urllib.request.urlretrieve urlretrieve -urllib2 urllib.request urllib2 -urllib2.Request urllib.request.Request Request -urllib2.OpenerDirector urllib.request.OpenerDirector OpenerDirector -urllib2.UnknownHandler urllib.request.UnknownHandler UnknownHandler -urllib2.HTTPHandler urllib.request.HTTPHandler HTTPHandler -urllib2.HTTPSHandler urllib.request.HTTPSHandler HTTPSHandler -urllib2.HTTPDefaultErrorHandler urllib.request.HTTPDefaultErrorHandler HTTPDefaultErrorHandler -urllib2.FTPHandler urllib.request.FTPHandler FTPHandler -urllib2.HTTPError urllib.request.HTTPError HTTPError -urllib2.HTTPErrorProcessor urllib.request.HTTPErrorProcessor HTTPErrorProcessor - -htmlentitydefs.name2codepoint html.entities.name2codepoint name2codepoint -HTMLParser html.parser HTMLParser -cPickle/pickle pickle pickle -thread/dummy_thread _thread/_dummy_thread thread - -os.getcwdu os.getcwd getcwdu -itertools.izip zip zip -sys.maxint sys.maxsize maxsize -unichr chr unichr -xrange range xrange -=============================== ====================================== ====================== - - -Output encoding now Unicode -=========================== - -If you want to catch stdout/stderr output, the output content is UTF-8 encoded -in Python 2, while it is Unicode strings in Python 3. You can use the OutputIO -stream to capture this output:: - - from django.utils.py3 import OutputIO - - try: - old_stdout = sys.stdout - out = OutputIO() - sys.stdout = out - # Do stuff which produces standard output - result = out.getvalue() - finally: - sys.stdout = old_stdout - -Dict iteritems/itervalues/iterkeys -================================== - -The iteritems(), itervalues() and iterkeys() methods of dictionaries do not -exist any more in Python 3, simply because they represent the default items() -values() and keys() behavior in Python 3. Therefore, to keep compatibility, -use similar functions from ``django.utils.py3``:: - - from django.utils.py3 import iteritems, itervalues, iterkeys - - my_dict = {'a': 21, 'b': 42} - for key, value in iteritems(my_dict): - # ... - for value in itervalues(my_dict): - # ... - for key in iterkeys(my_dict): - # ... - -Note that in Python 3, dict.keys(), dict.items() and dict.values() return -"views" instead of lists. Wrap them into list() if you really need their return -values to be in a list. - -http://docs.python.org/release/3.0.1/whatsnew/3.0.html#views-and-iterators-instead-of-lists - -Metaclass -========= - -The syntax for declaring metaclasses has changed in Python 3. -``django.utils.py3`` offers a compatible way to declare metaclasses:: - - from django.utils.py3 import with_metaclass - - class MyClass(with_metaclass(SubClass1, SubClass2,...)): - # ... - -Re-raising exceptions -===================== - -One of the syntaxes to raise exceptions (raise E, V, T) is gone in Python 3. -This is especially used in very specific cases where you want to re-raise a -different exception that the initial one, while keeping the original traceback. -So, instead of:: - - raise Exception, Exception(msg), traceback - -Use:: - - from django.utils.py3 import reraise - - reraise(Exception, Exception(msg), traceback) +Be cautious if you have to `slice bytestrings`_. +.. _slice bytestrings: http://docs.python.org/py3k/howto/pyporting.html#bytes-literals -- cgit v1.3 From 00ace01411b4cb558e71bfaf34cf42870e73092b Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 22 Jul 2012 10:29:07 +0200 Subject: [py3] Documented coding guidelines for Python 3. --- docs/conf.py | 1 + docs/topics/python3.txt | 123 ++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 99 insertions(+), 25 deletions(-) (limited to 'docs/topics/python3.txt') diff --git a/docs/conf.py b/docs/conf.py index 659115dfbd..39a280e464 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -94,6 +94,7 @@ pygments_style = 'trac' intersphinx_mapping = { 'python': ('http://docs.python.org/2.7', None), 'sphinx': ('http://sphinx.pocoo.org/', None), + 'six': ('http://packages.python.org/six/', None), } # Python's docs don't change every week. diff --git a/docs/topics/python3.txt b/docs/topics/python3.txt index e4bfc1bd9c..cfa38d9bec 100644 --- a/docs/topics/python3.txt +++ b/docs/topics/python3.txt @@ -2,42 +2,34 @@ Python 3 compatibility ====================== -Django 1.5 is the first version of Django to support Python 3. - -The same code runs both on Python 2 (≥2.6.5) and Python 3 (≥3.2). To -achieve this: - -- wherever possible, Django uses the six_ compatibility layer, -- all modules declare ``from __future__ import unicode_literals``. +Django 1.5 is the first version of Django to support Python 3. The same code +runs both on Python 2 (≥ 2.6.5) and Python 3 (≥ 3.2), thanks to the six_ +compatibility layer and ``unicode_literals``. .. _six: http://packages.python.org/six/ This document is not meant as a Python 2 to Python 3 migration guide. There -are many existing resources, including `Python's official porting guide`_. But -it describes guidelines that apply to Django's code and are recommended for -pluggable apps that run with both Python 2 and 3. +are many existing resources, including `Python's official porting guide`_. +Rather, it describes guidelines that apply to Django's code and are +recommended for pluggable apps that run with both Python 2 and 3. .. _Python's official porting guide: http://docs.python.org/py3k/howto/pyporting.html -.. module: django.utils.six - -django.utils.six -================ - -Read the documentation of six_. It's the canonical compatibility library for -supporting Python 2 and 3 in a single codebase. +Syntax requirements +=================== -``six`` is bundled with Django: you can import it as :mod:`django.utils.six`. +Unicode +------- -.. _string-handling: +In Python 3, all strings are considered Unicode by default. The ``unicode`` +type from Python 2 is called ``str`` in Python 3, and ``str`` becomes +``bytes``. -String handling -=============== +You mustn't use the ``u`` prefix before a unicode string literal because it's +a syntax error in Python 3.2. You must prefix byte strings with ``b``. -In Python 3, all strings are considered Unicode strings by default. Byte -strings must be prefixed with the letter ``b``. In order to enable the same -behavior in Python 2, every module must import ``unicode_literals`` from -``__future__``:: +In order to enable the same behavior in Python 2, every module must import +``unicode_literals`` from ``__future__``:: from __future__ import unicode_literals @@ -47,3 +39,84 @@ behavior in Python 2, every module must import ``unicode_literals`` from Be cautious if you have to `slice bytestrings`_. .. _slice bytestrings: http://docs.python.org/py3k/howto/pyporting.html#bytes-literals + +Exceptions +---------- + +When you capture exceptions, use the ``as`` keyword:: + + try: + ... + except MyException as exc: + ... + +This older syntax was removed in Python 3:: + + try: + ... + except MyException, exc: + ... + +The syntax to reraise an exception with a different traceback also changed. +Use :func:`six.reraise`. + + +.. module: django.utils.six + +Writing compatible code with six +================================ + +six is the canonical compatibility library for supporting Python 2 and 3 in +a single codebase. Read its `documentation `_! + +:mod:`six` is bundled with Django: you can import it as :mod:`django.utils.six`. + +Here are the most common changes required to write compatible code. + +String types +------------ + +The ``basestring`` and ``unicode`` types were removed in Python 3, and the +meaning of ``str`` changed. To test these types, use the following idioms:: + + isinstance(myvalue, six.string_types) # replacement for basestring + isinstance(myvalue, six.text_type) # replacement for unicode + isinstance(myvalue, bytes) # replacement for str + +Python ≥ 2.6 provides ``bytes`` as an alias for ``str``, so you don't need +:attr:`six.binary_type`. + +``long`` +-------- + +The ``long`` type no longer exists in Python 3. ``1L`` is a syntax error. Use +:data:`six.integer_types` check if a value is an integer or a long:: + + isinstance(myvalue, six.integer_types) # replacement for (int, long) + +``xrange`` +---------- + +Import :func:`six.moves.xrange` wherever you use ``xrange``. + +Moved modules +------------- + +Some modules were renamed in Python 3. The :mod:`django.utils.six.moves +` module provides a compatible location to import them. + +In addition to six' defaults, Django's version provides ``dummy_thread`` as +``_dummy_thread``. + +PY3 +--- + +If you need different code in Python 2 and Python 3, check :data:`six.PY3`:: + + if six.PY3: + # do stuff Python 3-wise + else: + # do stuff Python 2-wise + +This is a last resort solution when :mod:`six` doesn't provide an appropriate +function. -- cgit v1.3 From ebc89a800ac743bccb56810526c352fedd2aaa3e Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 22 Jul 2012 19:48:10 +0200 Subject: Fixed a broken link in the Python 3 docs. Thanks ptone for the report. --- docs/topics/python3.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'docs/topics/python3.txt') diff --git a/docs/topics/python3.txt b/docs/topics/python3.txt index cfa38d9bec..7c1cb53150 100644 --- a/docs/topics/python3.txt +++ b/docs/topics/python3.txt @@ -66,8 +66,8 @@ Use :func:`six.reraise`. Writing compatible code with six ================================ -six is the canonical compatibility library for supporting Python 2 and 3 in -a single codebase. Read its `documentation `_! +six_ is the canonical compatibility library for supporting Python 2 and 3 in +a single codebase. Read its documentation! :mod:`six` is bundled with Django: you can import it as :mod:`django.utils.six`. -- cgit v1.3 From ab6cd1c839b136cbc94178da433b2e97ab7f6061 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Wed, 25 Jul 2012 09:12:59 +0200 Subject: [py3] Updated dict-like data structures for Python 3. The keys/items/values methods return iterators in Python 3, and the iterkeys/items/values methods don't exist in Python 3. The behavior under Python 2 is unchanged. --- django/utils/datastructures.py | 135 +++++++++++++++----------- django/utils/six.py | 9 ++ docs/topics/python3.txt | 15 +++ tests/regressiontests/utils/datastructures.py | 68 +++++++------ 4 files changed, 140 insertions(+), 87 deletions(-) (limited to 'docs/topics/python3.txt') diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index 4d265ca719..bbd31ad36c 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -1,6 +1,7 @@ import copy import warnings from types import GeneratorType +from django.utils import six class MergeDict(object): @@ -31,38 +32,48 @@ class MergeDict(object): except KeyError: return default + # This is used by MergeDicts of MultiValueDicts. def getlist(self, key): for dict_ in self.dicts: - if key in dict_.keys(): + if key in dict_: return dict_.getlist(key) return [] - def iteritems(self): + def _iteritems(self): seen = set() for dict_ in self.dicts: - for item in dict_.iteritems(): - k, v = item + for item in six.iteritems(dict_): + k = item[0] if k in seen: continue seen.add(k) yield item - def iterkeys(self): - for k, v in self.iteritems(): + def _iterkeys(self): + for k, v in self._iteritems(): yield k - def itervalues(self): - for k, v in self.iteritems(): + def _itervalues(self): + for k, v in self._iteritems(): yield v - def items(self): - return list(self.iteritems()) + if six.PY3: + items = _iteritems + keys = _iterkeys + values = _itervalues + else: + iteritems = _iteritems + iterkeys = _iterkeys + itervalues = _itervalues + + def items(self): + return list(self.iteritems()) - def keys(self): - return list(self.iterkeys()) + def keys(self): + return list(self.iterkeys()) - def values(self): - return list(self.itervalues()) + def values(self): + return list(self.itervalues()) def has_key(self, key): for dict_ in self.dicts: @@ -71,7 +82,8 @@ class MergeDict(object): return False __contains__ = has_key - __iter__ = iterkeys + + __iter__ = _iterkeys def copy(self): """Returns a copy of this object.""" @@ -117,7 +129,7 @@ class SortedDict(dict): data = list(data) super(SortedDict, self).__init__(data) if isinstance(data, dict): - self.keyOrder = data.keys() + self.keyOrder = list(six.iterkeys(data)) else: self.keyOrder = [] seen = set() @@ -128,7 +140,7 @@ class SortedDict(dict): def __deepcopy__(self, memo): return self.__class__([(key, copy.deepcopy(value, memo)) - for key, value in self.iteritems()]) + for key, value in six.iteritems(self)]) def __copy__(self): # The Python's default copy implementation will alter the state @@ -162,28 +174,38 @@ class SortedDict(dict): self.keyOrder.remove(result[0]) return result - def items(self): - return zip(self.keyOrder, self.values()) - - def iteritems(self): + def _iteritems(self): for key in self.keyOrder: yield key, self[key] - def keys(self): - return self.keyOrder[:] - - def iterkeys(self): - return iter(self.keyOrder) - - def values(self): - return map(self.__getitem__, self.keyOrder) + def _iterkeys(self): + for key in self.keyOrder: + yield key - def itervalues(self): + def _itervalues(self): for key in self.keyOrder: yield self[key] + if six.PY3: + items = _iteritems + keys = _iterkeys + values = _itervalues + else: + iteritems = _iteritems + iterkeys = _iterkeys + itervalues = _itervalues + + def items(self): + return list(self.iteritems()) + + def keys(self): + return list(self.iterkeys()) + + def values(self): + return list(self.itervalues()) + def update(self, dict_): - for k, v in dict_.iteritems(): + for k, v in six.iteritems(dict_): self[k] = v def setdefault(self, key, default): @@ -226,7 +248,7 @@ class SortedDict(dict): Replaces the normal dict.__repr__ with a version that returns the keys in their sorted order. """ - return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()]) + return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in six.iteritems(self)]) def clear(self): super(SortedDict, self).clear() @@ -356,38 +378,41 @@ class MultiValueDict(dict): """Appends an item to the internal list associated with key.""" self.setlistdefault(key).append(value) - def items(self): - """ - Returns a list of (key, value) pairs, where value is the last item in - the list associated with the key. - """ - return [(key, self[key]) for key in self.keys()] - - def iteritems(self): + def _iteritems(self): """ Yields (key, value) pairs, where value is the last item in the list associated with the key. """ - for key in self.keys(): - yield (key, self[key]) - - def lists(self): - """Returns a list of (key, list) pairs.""" - return super(MultiValueDict, self).items() + for key in self: + yield key, self[key] - def iterlists(self): + def _iterlists(self): """Yields (key, list) pairs.""" - return super(MultiValueDict, self).iteritems() + return six.iteritems(super(MultiValueDict, self)) - def values(self): - """Returns a list of the last value on every key list.""" - return [self[key] for key in self.keys()] - - def itervalues(self): + def _itervalues(self): """Yield the last value on every key list.""" - for key in self.iterkeys(): + for key in self: yield self[key] + if six.PY3: + items = _iteritems + lists = _iterlists + values = _itervalues + else: + iteritems = _iteritems + iterlists = _iterlists + itervalues = _itervalues + + def items(self): + return list(self.iteritems()) + + def lists(self): + return list(self.iterlists()) + + def values(self): + return list(self.itervalues()) + def copy(self): """Returns a shallow copy of this object.""" return copy.copy(self) @@ -410,7 +435,7 @@ class MultiValueDict(dict): self.setlistdefault(key).append(value) except TypeError: raise ValueError("MultiValueDict.update() takes either a MultiValueDict or dictionary") - for key, value in kwargs.iteritems(): + for key, value in six.iteritems(kwargs): self.setlistdefault(key).append(value) def dict(self): diff --git a/django/utils/six.py b/django/utils/six.py index c74f9fa7df..e226bba09e 100644 --- a/django/utils/six.py +++ b/django/utils/six.py @@ -355,4 +355,13 @@ def with_metaclass(meta, base=object): ### Additional customizations for Django ### +if PY3: + _iterlists = "lists" +else: + _iterlists = "iterlists" + +def iterlists(d): + """Return an iterator over the values of a MultiValueDict.""" + return getattr(d, _iterlists)() + add_move(MovedModule("_dummy_thread", "dummy_thread")) diff --git a/docs/topics/python3.txt b/docs/topics/python3.txt index 7c1cb53150..3f799edac7 100644 --- a/docs/topics/python3.txt +++ b/docs/topics/python3.txt @@ -120,3 +120,18 @@ If you need different code in Python 2 and Python 3, check :data:`six.PY3`:: This is a last resort solution when :mod:`six` doesn't provide an appropriate function. + +.. module:: django.utils.six + +Customizations of six +===================== + +The version of six bundled with Django includes a few additional tools: + +.. function:: iterlists(MultiValueDict) + + Returns an iterator over the lists of values of a + :class:`~django.utils.datastructures.MultiValueDict`. This replaces + :meth:`~django.utils.datastructures.MultiValueDict.iterlists()` on Python + 2 and :meth:`~django.utils.datastructures.MultiValueDict.lists()` on + Python 3. diff --git a/tests/regressiontests/utils/datastructures.py b/tests/regressiontests/utils/datastructures.py index aea5393aab..dbc65d37a8 100644 --- a/tests/regressiontests/utils/datastructures.py +++ b/tests/regressiontests/utils/datastructures.py @@ -9,6 +9,7 @@ import warnings from django.test import SimpleTestCase from django.utils.datastructures import (DictWrapper, ImmutableList, MultiValueDict, MultiValueDictKeyError, MergeDict, SortedDict) +from django.utils import six class SortedDictTests(SimpleTestCase): @@ -25,19 +26,19 @@ class SortedDictTests(SimpleTestCase): self.d2[7] = 'seven' def test_basic_methods(self): - self.assertEqual(self.d1.keys(), [7, 1, 9]) - self.assertEqual(self.d1.values(), ['seven', 'one', 'nine']) - self.assertEqual(self.d1.items(), [(7, 'seven'), (1, 'one'), (9, 'nine')]) + self.assertEqual(list(six.iterkeys(self.d1)), [7, 1, 9]) + self.assertEqual(list(six.itervalues(self.d1)), ['seven', 'one', 'nine']) + self.assertEqual(list(six.iteritems(self.d1)), [(7, 'seven'), (1, 'one'), (9, 'nine')]) def test_overwrite_ordering(self): - """ Overwriting an item keeps it's place. """ + """ Overwriting an item keeps its place. """ self.d1[1] = 'ONE' - self.assertEqual(self.d1.values(), ['seven', 'ONE', 'nine']) + self.assertEqual(list(six.itervalues(self.d1)), ['seven', 'ONE', 'nine']) def test_append_items(self): """ New items go to the end. """ self.d1[0] = 'nil' - self.assertEqual(self.d1.keys(), [7, 1, 9, 0]) + self.assertEqual(list(six.iterkeys(self.d1)), [7, 1, 9, 0]) def test_delete_and_insert(self): """ @@ -45,18 +46,22 @@ class SortedDictTests(SimpleTestCase): at the end. """ del self.d2[7] - self.assertEqual(self.d2.keys(), [1, 9, 0]) + self.assertEqual(list(six.iterkeys(self.d2)), [1, 9, 0]) self.d2[7] = 'lucky number 7' - self.assertEqual(self.d2.keys(), [1, 9, 0, 7]) + self.assertEqual(list(six.iterkeys(self.d2)), [1, 9, 0, 7]) - def test_change_keys(self): - """ - Changing the keys won't do anything, it's only a copy of the - keys dict. - """ - k = self.d2.keys() - k.remove(9) - self.assertEqual(self.d2.keys(), [1, 9, 0, 7]) + if not six.PY3: + def test_change_keys(self): + """ + Changing the keys won't do anything, it's only a copy of the + keys dict. + + This test doesn't make sense under Python 3 because keys is + an iterator. + """ + k = self.d2.keys() + k.remove(9) + self.assertEqual(self.d2.keys(), [1, 9, 0, 7]) def test_init_keys(self): """ @@ -68,18 +73,18 @@ class SortedDictTests(SimpleTestCase): tuples = ((2, 'two'), (1, 'one'), (2, 'second-two')) d = SortedDict(tuples) - self.assertEqual(d.keys(), [2, 1]) + self.assertEqual(list(six.iterkeys(d)), [2, 1]) real_dict = dict(tuples) - self.assertEqual(sorted(real_dict.values()), ['one', 'second-two']) + self.assertEqual(sorted(six.itervalues(real_dict)), ['one', 'second-two']) # Here the order of SortedDict values *is* what we are testing - self.assertEqual(d.values(), ['second-two', 'one']) + self.assertEqual(list(six.itervalues(d)), ['second-two', 'one']) def test_overwrite(self): self.d1[1] = 'not one' self.assertEqual(self.d1[1], 'not one') - self.assertEqual(self.d1.keys(), self.d1.copy().keys()) + self.assertEqual(list(six.iterkeys(self.d1)), list(six.iterkeys(self.d1.copy()))) def test_append(self): self.d1[13] = 'thirteen' @@ -115,8 +120,8 @@ class SortedDictTests(SimpleTestCase): def test_copy(self): orig = SortedDict(((1, "one"), (0, "zero"), (2, "two"))) copied = copy.copy(orig) - self.assertEqual(orig.keys(), [1, 0, 2]) - self.assertEqual(copied.keys(), [1, 0, 2]) + self.assertEqual(list(six.iterkeys(orig)), [1, 0, 2]) + self.assertEqual(list(six.iterkeys(copied)), [1, 0, 2]) def test_clear(self): self.d1.clear() @@ -178,12 +183,12 @@ class MergeDictTests(SimpleTestCase): self.assertEqual(mm.getlist('key4'), ['value5', 'value6']) self.assertEqual(mm.getlist('undefined'), []) - self.assertEqual(sorted(mm.keys()), ['key1', 'key2', 'key4']) - self.assertEqual(len(mm.values()), 3) + self.assertEqual(sorted(six.iterkeys(mm)), ['key1', 'key2', 'key4']) + self.assertEqual(len(list(six.itervalues(mm))), 3) - self.assertTrue('value1' in mm.values()) + self.assertTrue('value1' in six.itervalues(mm)) - self.assertEqual(sorted(mm.items(), key=lambda k: k[0]), + self.assertEqual(sorted(six.iteritems(mm), key=lambda k: k[0]), [('key1', 'value1'), ('key2', 'value3'), ('key4', 'value6')]) @@ -201,10 +206,10 @@ class MultiValueDictTests(SimpleTestCase): self.assertEqual(d['name'], 'Simon') self.assertEqual(d.get('name'), 'Simon') self.assertEqual(d.getlist('name'), ['Adrian', 'Simon']) - self.assertEqual(list(d.iteritems()), + self.assertEqual(list(six.iteritems(d)), [('position', 'Developer'), ('name', 'Simon')]) - self.assertEqual(list(d.iterlists()), + self.assertEqual(list(six.iterlists(d)), [('position', ['Developer']), ('name', ['Adrian', 'Simon'])]) @@ -224,8 +229,7 @@ class MultiValueDictTests(SimpleTestCase): d.setlist('lastname', ['Holovaty', 'Willison']) self.assertEqual(d.getlist('lastname'), ['Holovaty', 'Willison']) - self.assertEqual(d.values(), ['Developer', 'Simon', 'Willison']) - self.assertEqual(list(d.itervalues()), + self.assertEqual(list(six.itervalues(d)), ['Developer', 'Simon', 'Willison']) def test_appendlist(self): @@ -260,8 +264,8 @@ class MultiValueDictTests(SimpleTestCase): 'pm': ['Rory'], }) d = mvd.dict() - self.assertEqual(d.keys(), mvd.keys()) - for key in mvd.keys(): + self.assertEqual(list(six.iterkeys(d)), list(six.iterkeys(mvd))) + for key in six.iterkeys(mvd): self.assertEqual(d[key], mvd[key]) self.assertEqual({}, MultiValueDict().dict()) -- cgit v1.3