Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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...

like image 425
exhuma Avatar asked Nov 02 '22 08:11

exhuma


1 Answers

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.

like image 155
Martijn Pieters Avatar answered Nov 09 '22 15:11

Martijn Pieters