Python 3 - style question

205 views
Skip to first unread message

Aymeric Augustin

unread,
Aug 9, 2012, 4:36:12 PM8/9/12
to django-d...@googlegroups.com
Hello,

One of the first lessons in the tutorial is to define a __unicode__ method. In Python 3, __unicode__ is replaced by __str__ (and __str__ by __bytes__, but that method won't be needed in general).

Writing these methods in a way works on both Python 2 and 3 proves surprisingly messy. I'd like some feedback before choosing a technique, applying it to Django, and documenting it.

Here are a few proposals. Which one do you prefer? Do you have better ideas?


* Proposal 1 — the very explicit way
+ explicit
+ no runtime overhead
- not DRY
- much boilerplate code

from __future__ import unicode_literals
from django.utils import six

class MyClass(object):
if six.PY3:
def __str__(self):
return "text ..."
else:
def __str__(self):
return self.__unicode__().encode('utf-8')
def __unicode__(self):
return "text ..."


* Proposal 2 — the Python 3 way
+ explicit
+ no runtime overhead
- 3 lines of boilerplate code

from __future__ import unicode_literals
from django.utils import six

class MyClass(object):
def __str__(self):
return "text ..."
if not six.PY3:
__unicode__ = __str__
__str__ = lambda self: self.__unicode__().encode('utf-8')


* Proposal 3 — the magic mixin
+ no boilerplate code
- not explicit
- requires writing a __unicode__ method which was removed in Python 3: this is non-educational for Python 2 programmers learning Python 3 and complete nonsense for Python 3 programmers who have never been exposed to Python 2.

from __future__ import unicode_literals
from django.utils.encoding import StrAndUnicode

class MyClass(StrAndUnicode, object):
def __unicode__(self):
return "text ..."


* Proposal 4 — the non-unicode way
- on Python 2, __unicode__ performs an unnecessary encode / decode
- on Python 2, __unicode__ will fail if the system encoding isn't utf-8 (which may happen for a variety of reasons)

from __future__ import unicode_literals
from django.utils import six

class MyClass(object):
def __str__(self):
result = "text ..."
if six.PY3:
result = result.encode('utf-8')
return result


At this point I tend to prefer the version 2, because it's explicit, short and in line with our goal to write Python 3 code that also works on Python 2. What about you?

Best regards,

--
Aymeric.

charettes

unread,
Aug 9, 2012, 4:58:07 PM8/9/12
to django-d...@googlegroups.com
I think this will only be an issue for django application maintainers.

IMHO, projects target a specific version of python and won't have to provide python 2-3 compatibility. Am I wrong?

Russell Keith-Magee

unread,
Aug 9, 2012, 7:35:17 PM8/9/12
to django-d...@googlegroups.com
On Fri, Aug 10, 2012 at 4:58 AM, charettes <chare...@gmail.com> wrote:
> I think this will only be an issue for django application maintainers.
>
> IMHO, projects target a specific version of python and won't have to provide
> python 2-3 compatibility. Am I wrong?

Yes and no.

On the one hand -- yes. Jo(sephin)e Public is here to write some code,
and it's going to be for their own purposes, which means they've
already picked a Python version, and so making their code
cross-version isn't important.

However, on the other hand, if you're "doing it right™", *every* app
is a reusable app, and teaching best practices is important. Like it
or not, Django is a major part of the Python ecosystem, and a lot of
people look at us as an example of what best practices look like.

My suggestion would be to look at a fifth option. Lets remember that
this is a training exercise, and for many of our user base, this is
their first exposure to Python. If we overload complexity in the
basics, they're going to come to the conclusion that Python is nasty
and horrible.

So - let's write the initial tutorials targeting a specific version,
but provide hints/directions indicating that there is a bigger picture
to consider:

a) There should also be an aside at the start of section 1 of the
tutorial quickly explaining the Python version landscape to those that
aren't familiar with it, and explaining how the tutorial will handle
this landscape.

b) Write the docs as Py2, with "Py3 admonition" whenever there's a
difference between the two. I haven't done a full audit of the
tutorial, but there shouldn't be *too* many of these.

c) Add a new tutorial section indicating how to write cross-version
Python. For that document, I agree that Aymeric's option 2 looks
attractive, because it's the version that is the easiest to clean up
when the time comes to deprecate Py2 (although I wouldn't complain if
we packaged a pre-canned version of the lambda function in
django.utils)

Over time, we could change the primacy of the docs from Py2 native
(with Py3 warnings) to Py3 native (with Py2 warnings), until we
finally deprecate Py2.

In the interest of keeping the tutorial clear, I *might* be convinced
to drop step (b) entirely. This would mean we still have a Py2
tutorial, but with a couple of clear pointers towards best practices
(one indicating that there is a bigger picture to consider, and one
showing how to paint that picture). However, that would leave native
Py3-only users high and dry, which isn't exactly ideal.

Yours,
Russ Magee %-)

Daniel Sokolowski

unread,
Aug 9, 2012, 11:28:42 PM8/9/12
to django-d...@googlegroups.com
I prefer Proposal 2 out of the list, and regarding Russell's point I
believe that the tutorial ought to promote Python 3 and be written from
that perspective with Python 2 exceptions - because exactly of Django's
importance in the Python landscape.

Thanks and good day.

On 09/08/2012 19:35, Russell Keith-Magee wrote:
> On Fri, Aug 10, 2012 at 4:58 AM, charettes <chare...@gmail.com> wrote:
>> I think this will only be an issue for django application maintainers.
>>
>> IMHO, projects target a specific version of python and won't have to provide
>> python 2-3 compatibility. Am I wrong?
> Yes and no.
>
> On the one hand -- yes. Jo(sephin)e Public is here to write some code,
> and it's going to be for their own purposes, which means they've
> already picked a Python version, and so making their code
> cross-version isn't important.
>
> However, on the other hand, if you're "doing it right�", *every* app
--
Daniel Sokolowski
Web Engineer
Danols Web Engineering
http://webdesign.danols.com/
Office: 613-817-6833
Fax: 613-817-5340
Toll Free: 1-855-5DANOLS
Kingston, ON K7L 1H3, Canada


Notice of Confidentiality:
The information transmitted is intended only for the person or entity to which it is addressed and may contain confidential and/or privileged material. Any review re-transmission dissemination or other use of or taking of any action in reliance upon this information by persons or entities other than the intended recipient is prohibited. If you received this in error please contact the sender immediately by return electronic transmission and then immediately delete this transmission including all attachments without copying distributing or disclosing same.

Vinay Sajip

unread,
Aug 10, 2012, 12:56:15 PM8/10/12
to Django developers
I think Option 2 is better, for the reasons you state.

Regards,

Vinay Sajip

Łukasz Rekucki

unread,
Aug 10, 2012, 4:10:22 PM8/10/12
to django-d...@googlegroups.com
On 10 August 2012 18:56, Vinay Sajip <vinay...@yahoo.co.uk> wrote:
> I think Option 2 is better, for the reasons you state.
>

How about wrapping those 3 lines of code into a class decorator
(preferably named more explicit then StrAndUnicode) ? That would be at
least a little DRY.

--
Łukasz Rekucki

Simon Meers

unread,
Aug 10, 2012, 6:45:05 PM8/10/12
to django-d...@googlegroups.com
> On 10 August 2012 18:56, Vinay Sajip <vinay...@yahoo.co.uk> wrote:
>> I think Option 2 is better, for the reasons you state.

+1. And it's not too entangled to be easily stripped out if/when
Python 2 support is removed.

On 11 August 2012 06:10, Łukasz Rekucki <lrek...@gmail.com> wrote:
> How about wrapping those 3 lines of code into a class decorator
> (preferably named more explicit then StrAndUnicode) ? That would be at
> least a little DRY.

+1. It won't mess with the inheritance hierarchy, and is explicit
enough (depending on the name), whilst encapsulating the boilerplate
elegantly. @python2_unicode_compatible?

(Though, @-syntax class decorators are only available from 2.6+, but
I'm guessing we won't be able to drop 2.5 support at the same time as
picking up 3. I guess we could just tidy up when we do.)

Simon

Alex Gaynor

unread,
Aug 10, 2012, 6:46:07 PM8/10/12
to django-d...@googlegroups.com

--
You received this message because you are subscribed to the Google Groups "Django developers" group.
To post to this group, send email to django-d...@googlegroups.com.
To unsubscribe from this group, send email to django-develop...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/django-developers?hl=en.


Trunk is 2.6+, so we're fine on that score.

Alex

--
"I disapprove of what you say, but I will defend to the death your right to say it." -- Evelyn Beatrice Hall (summarizing Voltaire)
"The people's good is the highest law." -- Cicero

Aymeric Augustin

unread,
Aug 11, 2012, 5:00:55 AM8/11/12
to django-d...@googlegroups.com
Hello,

Thanks for all your answers. A decorator will indeed be the cleanest solution.

This idea was suggested on IRC too but withdrawn because "you can't bind back to the class". Well, as far as I can tell, the code below works. Please let me know if you see any issues with this implementation.

# django.utils.encoding

from django.utils import six

def python_2_unicode_compatible(klass):
if not six.PY3:
klass.__unicode__ = klass.__str__
klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
return klass

# in Django or user code

from __future__ import unicode_literals
from django.utils.encoding import python_2_unicode_compatible

@python_2_unicode_compatible
class MyClass(object):
def __str__(self):
result = "text ..."

The only drawback of this solution is that it hardcodes the 'utf-8' encoding. I don't see that as a problem since Django already makes this assumption everywhere, especially in the smart_text/bytes functions.


The digression on the tutorial is interesting. I'd like to reach the "Py3 with Py2 warnings" step as fast as possible but we can't do that until a significant part of the Django ecosystem has itself migrated to Python 3.

Anyway, I'm not working on the documentation at this time. I'm only keeping API docs in sync with the code. This will be easier to discuss when the port is finished, and we have a better view of its consequences.

Best regards,

--
Aymeric.

Aymeric Augustin

unread,
Aug 11, 2012, 1:59:12 PM8/11/12
to django-d...@googlegroups.com
On 11 août 2012, at 11:00, Aymeric Augustin wrote:
> Thanks for all your answers. A decorator will indeed be the cleanest solution.

Given the large number of existing __unicode__ methods (66 in django, 375 in the tests) I've written a custom 2to3 fixer to perform the transformation.

https://github.com/aaugustin/django/commits/py3-2to3

To try it, checkout the branch and run:
$ django2to3.py django tests
(assuming django/bin is on your path).

Do you think the Django distribution include include this fixer? It could be useful to developers porting pluggable apps, but I can't vouch for its quality — it's the first time I touch 2to3. Should I just put in extras/ with some basic instructions?

Best regards,

--
Aymeric.
Reply all
Reply to author
Forward
0 new messages