How to import object from builtins affecting just one class?

528 views Asked by At

I am converting code from python2 to python3 for newstyle classes using future. My project is in Django 1.11

I have a class in forms.py as:

class Address:
    ...rest of code...

class AddressForm(Address, forms.ModelForm):
    ...rest of code...

in Python 2

which is converted to :

from buitlins import object
class Address(object):
        ...rest of code...

class AddressForm(Address, forms.ModelForm):
    ...rest of code...

in Python 3

I have a selenium test that fails when this Form is invoked after it is converted to Python3 with the following error:

File "<path_to_venv>/local/lib/python2.7/site-packages/django/utils/six.py", line 842, in <lambda>
klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
File "<path_to_venv>/local/lib/python2.7/site-packages/future/types/newobject.py", line 78, in __unicode__
s = type(self).__str__(self)
RuntimeError: maximum recursion depth exceeded

However, when I remove the import from buitlins import object the test passes.

But as I have added a future check, I get a future difference error & thus every class has to be converted to newstyle. I want it to work in both Python2 and Python3.

Is there a way this module builtins module import can affect just one class and not others in the forms.py file. Or is there some other method to handle this?

3

There are 3 answers

3
Patrick Haugh On BEST ANSWER

The problem you're running up against seems to be from two different Python 2 modernization tools fighting. You seem to be using the python_2_unicode_compatible decorator from django.utils.six

def python_2_unicode_compatible(klass):
    """
    A decorator that defines __unicode__ and __str__ methods under Python 2.
    Under Python 3 it does nothing.
    To support Python 2 and 3 with a single code base, define a __str__ method
    returning text and apply this decorator to the class.
    """
    if PY2:
        if '__str__' not in klass.__dict__:
            raise ValueError("@python_2_unicode_compatible cannot be applied "
                             "to %s because it doesn't define __str__()." %
                             klass.__name__)
        klass.__unicode__ = klass.__str__
        klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
    return klass

and inheriting from newobject, which has this __unicode__ method

def __unicode__(self):
    # All subclasses of the builtin object should have __str__ defined.
    # Note that old-style classes do not have __str__ defined.
    if hasattr(self, '__str__'):
        s = type(self).__str__(self)
    else:
        s = str(self)
    if isinstance(s, unicode):
        return s
    else:
        return s.decode('utf-8')

And because the two have slightly different strategies for providing both __unicode__ and __str__ methods, they ed up calling each other infinitely, which leads to your recursion error.

The module that provides builtins.object provides its own python_2_unicode_compatible decorator. Have you tried using that over the one from django.utils.six?

4
ruohola On

This is the python2 way.

class Address(object):

In python3 classes inherit the object implicitly, so it should be like this;

class Address:
0
Mark On

Ran into this today, and Patrick Haugh mostly describes the problem, except that six and python_2_unicode_compatible is not referenced in django 1.11, the version in the question and the one I am using. In our case, the problem was that a django model was inheriting from a mixin, which inherited from future.builtins.newobject.

  1. newobject (from builtins import object) adds an attribute called unicode: https://github.com/PythonCharmers/python-future/blob/master/src/future/types/newobject.py#L41
  2. django admin has a logging feature creates a LogEntry containing a text representation of the object. https://github.com/django/django/blob/stable/1.11.x/django/contrib/admin/options.py#L741
  3. The text representation used is object.__unicode__: https://github.com/django/django/blob/stable/1.11.x/django/utils/encoding.py#L77
  4. __unicode__ is a wrapper around __str__ in the future package
  5. __str__ for django db models is implemented as a wrapper around unicode in django https://github.com/django/django/blob/stable/1.11.x/django/db/models/base.py#L595

We don't have a great solution, apart from explicitly importing future as from builtins import object as future_object if we need access to both, and disabling the entire fix by running futurize --stage2 -x object instead of futurize --stage2