6

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?

ruohola
  • 21,987
  • 6
  • 62
  • 97
Deesha
  • 538
  • 8
  • 27

3 Answers3

6

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?

Patrick Haugh
  • 59,226
  • 13
  • 88
  • 96
  • I tried `class Address(object)`: without `from builtins import object` and the test passes. But since `newstyle` is in the list of checks in the CI CD, it needs `from builtins import object` else there is difference in futurize error. – Deesha Aug 29 '18 at 14:40
  • The module that provides `builtins.object` provides its own [`python_2_unicode_compatible`](http://python-future.org/reference.html?highlight=python%20unicode%20compatible#future.utils.python_2_unicode_compatible) decorator. Have you tried using that over the one from `django.utils.six`? – Patrick Haugh Aug 29 '18 at 14:51
  • Just tried this [python_2_unicode_compatible](http://python-future.org/reference.html?highlight=python%20unicode%20compatible#future.utils.python_2_unicode_compatible), the decorator worked. Thanks alot. This was really helpful :) – Deesha Aug 29 '18 at 14:57
0

This is the python2 way.

class Address(object):

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

class Address:
ruohola
  • 21,987
  • 6
  • 62
  • 97
  • [old-style and new-style classes in Python 2.7?](https://stackoverflow.com/questions/13816849/old-style-and-new-style-classes-in-python-2-7) isn't it the the other way round? Moving from py2 to py3 – Deesha Aug 29 '18 at 13:47
  • 1
    In Python 2 `class Address:` is an old-style class and `class Address(object):` is a new-style class. In Python 3, all classes are new-style. For the transition, you should change all of your classes to `class Adddress(object):`. The `from builtins import object` is importing a shim that knows about Python 3 interfaces like `__str__` and `__next__` that Python 2 doesn't have. – Patrick Haugh Aug 29 '18 at 13:50
  • @PatrickHaugh that makes sense. But I still wonder the class that is being converted to newstyle doesn't use `__str__` and `__next__` anywhere. – Deesha Aug 29 '18 at 13:56
  • `object` defines a `__str__` method, so your `Address(object)` class will inherit that method. – Patrick Haugh Aug 29 '18 at 14:00
0

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

Mark
  • 181
  • 1
  • 3