On Feb 20, 9:23 am, Florian Ledermann <[email protected]>
wrote:
..
> Maybe this would be an interesting starting point for our Python 3
> coding session next time, he is also documenting some interesting design
> and coding aspects on his blog in the process.

Since it's vaguely relevant, porting python 2 code to python 3
compatible
isn't actually as painful as people generally seem to think. I
converted
Axon (Kamaelia's core concurrency library) to python 3 compatibility
over
the course of one evening last week. The code now runs under both
python
2 and python 3.

Similarly if extension modules have been coded using Pyrex, then
converting them to Cython seems the simplest approach to getting
python 3 compatibility. (Again, this was relatively painless for
the set of python extension bindings I maintain - python-dvb3 etc)

Things I've noted that helps in having a single codebase are:

    * Detecting python version by using an import. For example the
      Queue module changed name. So you can determine python version
      like this:

      try:
           import Queue as queue
           python_version = 2
      except ImportError:
            import queue
            python_version = 3

     (This is primarily more useful than using sys.version_info[0]
      when you're actually interested in a module that has changed
      name of course :-)

   * Creating a utility module to alias things like xrange (python2)
     and range (python3's version) that changed name to a common name
     (I picked vrange) and then using that.

   * Converting python2 idioms to python3 ones, again using a utility
     library helps. For example, in python 2 you might do this:

     def fib():
         a,b = 1,1
         while 1:
             yield a
             a,b = b, a+b

     >>> g = fib()
     >>> g.next()
     1
     >>> g.next()
     1
     >>> g.next()
     2

     In python 3, the way you use generators has changed:
     >>> g = fib()
     >>> next(g)
     1
     >>> next(g)
     1
     >>> next(g)
     2

     Under the hood, .next has been renamed .__next__, so you can
     create a python2 specific version of next to handle this.

   * Similarly, if you're creating things with the same API, creating
     an aliases for __next__ and next so that the same code runs under
     both seems a pragmatic approach.

     class MyGen( ...):
         def __next__(self):
             ...
         next = __next__

   * Some syntactic changes can be dealt with by rewriting code. For
     example these two code formats are python 2.5 (and earlier) or
     python3 specific:

     try: # Python 2.5 and earlier
         1/0
     except ZeroDivisionError, e:
         print("BANG!", e)

     try:  # Python 3 (Also python 2.6)
         1/0
     except ZeroDivisionError as e:
         print("BANG!", e)

     The workaround to have a single piece of code for this is a
little
     hacky, but works reliably which looks like this:

     try:  # Python 3
         1/0
     except ZeroDivisionError:
         e = sys.exc_info()[1]
         print("BANG!", e)

     It's far from perfect, but works all the way back to python 2.3
(which
     I still need to support for example).

   *  Other changes which are a pain are changes to metaclass syntax
from
      this:
        class MyBaseObject(object):
            __metaclass__ = MyMetaClassType
            ...

      To this:
        class MyBaseObject(object, metaclass=MyMetaClassType):
            ...

      The way round this is to use conditional inclusion - to put the
minimal
      python 2 version in one file and a minimal python 3 version in
another
      file, and then import them into the original file. eg:

      import sys
      if sys.version_info[0] == 2:
          from actual_code_python2 import *
      else:
          from actual_code_python3 import *

      setup.py will get confused by one file or the other depending on
which
      version of python you're installing the module under.

    * The final thing which I hit was relative imports. In python 2 if
      you have a package that looks like this:
         setup.py
         MyPackage/Foo.py
         MyPackage/Bar.py
         MyPackage/Baz.py

      Then Foo can import Baz by simply doing "import Baz". In python
3,
      this changes to "from . import Baz". This breaks under various
      versions of python 2. The obvious way round that is to change
all
      the imports to absolute imports instead - ie to change Foo's
      imports to look like "import MyPackage.Baz as Baz"

That said, once you've made these sorts of changes, they seem
relatively
easy to maintain. The vast bulk of changes I ended up doing were
related
to changing print statements :-)


Michael.

-- 
To post: [email protected]
To unsubscribe: [email protected]
Feeds: http://groups.google.com/group/python-north-west/feeds
More options: http://groups.google.com/group/python-north-west

Reply via email to