Python 2 & 3 compatibility with `super` and classes who were old-style in Py2 but became new-style in Py3

StackOverflow https://stackoverflow.com/questions/20886484

Question

I have a project which uses SafeConfigParser and I want it to be Python2 and 3 compatible. Now, SafeConfigParser is deprecated since Python 3.2 and I find the deprecation warning annoying. So I went about my business to remedy that.

First (and much older, already solved problem): SafeConfigParser is an old-style class in Python2 so I cannot call super in my descendant class. To have a more consistent behaviour, I wrote the following:

try:
    # Python 2
    class ConfigResolverBase(object, SafeConfigParser):
        """
        A default "base" object simplifying Python 2 and Python 3
        compatibility.
        """
        pass
except TypeError:
    # Python 3
    class ConfigResolverBase(SafeConfigParser):
        """
        A default "base" object simplifying Python 2 and Python 3
        compatibility.
        """
        pass

This worked fine to make the class new-style if necessary. To get rid of the DeprecationWarning I changed the code into this:

if sys.hexversion < 0x030000F0:
    # Python 2
    class ConfigResolverBase(object, SafeConfigParser):
        """
        A default "base" object simplifying Python 2 and Python 3
        compatibility.
        """
        pass
else:
    # Python 3
    class ConfigResolverBase(ConfigParser):
        """
        A default "base" object simplifying Python 2 and Python 3
        compatibility.
        """
        pass

Along the way I also fixed a line I missed to change earlier:

@@ -275,7 +276,7 @@ class Config(ConfigResolverBase):
             have_default = False

         try:
-            value = SafeConfigParser.get(self, section, option, **kwargs)
+            value = super(Config, self).get(section, option, **kwargs)
             return value
         except (NoSectionError, NoOptionError) as exc:
             if have_default:

And that change caused an interesting error:

AttributeError: 'Config' object has no attribute '_sections'

Which let me to believe that __init__ of ConfigParser was not called. And indeed making the following change fixed that:

- class ConfigResolverBase(object, SafeConfigParser):
+ class ConfigResolverBase(SafeConfigParser, object):

My code is now working fine for both Python 2 and 3, but whant I am unsure about is: Is the proxy returned by super always the same?" In my case I inherit from both object and SafeConfigParser. Swapping the two bases in my class definition made super return the right one. But is this guaranteed to be stable across all Python implementations on all platforms? Or should I explicitly call SafeConfigParser.get(self, ...)? Which is after-all the "old way" of calling the base...

Was it helpful?

Solution

Yes, it is guaranteed stable across Python versions. The search order is called the Method Resolution Order, or MRO, and this order has been the same since Python 2.3.

For more details on how the order is determined, see the Python 2.3 Method Resolution Order documentation.

You can inspect the MRO by looking at the .__mro__ attribute of a given class; it is a tuple of the classes in method-resolution order.

OTHER TIPS

I was also doing some work using SafeConfigParser. To make it work with old-style and new-style classes, both, you can check if it is a subclass of object or not. New-style classes inherit from object, whereas old-style classes don't.

Example:

class MyClass(SafeConfigParser):
    def __init__(self):
        if issubclass(SafeConfigParser, object):
            # new style class, call super
            super(MyClass, self).__init__()
        else:
            # old style class, call __init__ manually
            SafeConfigParser.__init__(self)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top