14

I am trying to make some of my code Python 2 and 3 compatible.

At the moment I am struggling with functions like range/xrange and methods like dict.items/dict.iteritems. Ideally I would like my code to be able to use the former in Python 3.x and the latter in Python 2.x.

Using if/else seems to me to be the easiest way to implement this:

if py >= 3:
    for item in array.items()
    ...
else:
    for item in array.iteritems()

However, doing like that results in lots of repeated and ugly code. Is there a better way to do that using only the standard library? Can I just state somewhere at the beginning of the code to always use range/dict.items if py >= 3 and xrange/dict.iteritems if not?

Is it possible to do something like this?

if py < 3:
    use xrange as range

I have looked around and I know that several libraries, like six o futurize) are used to solve this issue. However I am working on a server that run only python 2.7 and I am not allowed to install any extra libraries on it. I have some python3 code I would like to use but I also want to maintain only one version of the code.

alec_djinn
  • 10,104
  • 8
  • 46
  • 71
  • `if py < 3: range = xrange`? It is trickier with `[iter]items`, though. Have a look at https://docs.python.org/3/howto/pyporting.html – jonrsharpe May 24 '15 at 10:38
  • 3
    Don't reinvent the wheel. Why the restriction to use standard library? https://pypi.python.org/pypi/six – wim May 24 '15 at 10:40
  • 4
    Why negative votes on the question? Isn't clear enough? – alec_djinn May 24 '15 at 10:40
  • I presume because there are lots of resources and related questions (see sidebar -->) on writing 2.x/3.x-compatible code already, and no evidence in the question that you've put any effort into your own research. Also *"best way"* suggests opinion-based answers; do you have any objective criteria? – jonrsharpe May 24 '15 at 10:43
  • I know there are ways using other libraries, but my point is what is the best way to do it if I want to use only the standard one? Is there a nice and fairly easy way to get around that ugly code or not? Am I really pushed to use another library in this case? – alec_djinn May 24 '15 at 10:44
  • 3
    I would suggest using the [`six`](https://pypi.python.org/pypi/six/) module. If you don't want to have to install it, you can still look at its code and see how it dealt with many of these issues and just manually add them selectively to your own code. – martineau May 24 '15 at 10:48
  • @jonrsharpe I am crawling the web looking for solutions and I am not finding anything I am satisfied with. I don't post questions for sport... If you find other posts discussing the very same question please link it. Also, for 'best', I mean 'not repeated' in this case.. if I use xrange 100 times in my code I don't want to include 100 if/else. – alec_djinn May 24 '15 at 10:49
  • So, I guess your `array` is really a dictionary... – martineau May 24 '15 at 10:52
  • Yes in this particular case it is. But the aim of the post is to find a general good way to write compatible code using nothing but the standard lib. – alec_djinn May 24 '15 at 10:57
  • @alec_djinn that may be the case but, as it stands, there is *no evidence of that* in the question. What have you read, what have you learned and what still escapes you? What are the reasons behind your constraints? You should never expect to find *"the very same question"*, instead look to apply general principles to your specific problem. – jonrsharpe May 24 '15 at 11:07
  • @jonrsharpe I agree, you are right. That is why I specified --using the standard library-- because in other posts there were already suggestions using six, futurize, etc... I may not have clarified it enough in my post. – alec_djinn May 24 '15 at 11:12
  • 5
    i'm wondering, why ppl try to force OP to use 3d part libraries, facepalm. It's just like jQuery kids......they really think that they know JavaScript, same here. For OP, in most cases you can loop through dict in other ways - by keys or values by example. It would be not iterator and more "simple" way, but he supported by all versions of Python without bicycles like "six". And the last, you should not write compatible code, by doing that you will not use the full force of py2 or py3 and as result will have a some average, not optimized and hard to debug code (due to compatibility layer) – Reishin May 24 '15 at 12:42
  • 2
    @alec_djinn Python3 is/was breaking backwards compatibility from python2. It was always that way and it was designed that way. This means if you want to support both, you need to have a real compatibility layer like for example _six_ which is trusted and tested and already proved in large projects (eg Django). **Not one that you've hacked together yourself in a few days.** – wim May 26 '15 at 00:14
  • @wim I understand your point, however, since my code is already very well compatible with both python2 and 3 I was looking for an "easy hack" to solve some minor issues mostly due to range and iteritems. But as far I got from all your suggestions It seams that the best is to use a compatibility layer. – alec_djinn May 26 '15 at 07:17
  • 1
    OK, fair enough .. easy hacks certainly have their place and it's good that you want to maintain python3 support even though the server is at 2.7. Just be aware that you may be bumping your head against subtle problems and complications that other people have already done the time with, certainly py 2/3 cross compatibility is a *non-trivial* issue although it can seem deceptively simple at first. – wim May 26 '15 at 08:02

5 Answers5

16

The simple, "Don't Make Me Think!" solution I use is to start simple scripts with:

#!/usr/bin/env python
# just make sure that Python 3 code runs fine with 2.7+ too ~98% of the time :)
from __future__ import (division, print_function, absolute_import,
                        unicode_literals)
from builtins import int
try:
    from future_builtins import ascii, filter, hex, map, oct, zip
except:
    pass
import sys
if sys.version_info.major > 2:
    xrange = range

(Extra tip to stop most pep8 linters for unnecessarily yelling at you for this: move last 3 lines inside and at the top of the try block above)

But the only case I use this is basically "shell scripts that were too large and hairy so I quickly rewrote them to Python and I just want them to run under both Python 2 and 3 with 0 dependencies". Please do NOT use this in real application/library code until you know exactly what are the consequences of all the lines above, and if they are enough for your use case.

Also, the "solution" in this case for .iteritems is "just don't use it", ignore memory use optimizations and just always use .items instead - if this matters, it means you're not writing a "0 dependencies simple script" anymore, so just pick Python 3 and code for it (or Python 2 if you need to pretend we're in 2008).

Also, check these resources to get a proper understanding:


(NOTE: I'm answering this already answered question mainly because the accepted answers roughly translates to "you are stupid and this is dumb" and I find this very rude for an SO answer: no matter how dumb the question, and how "wrong" to actually answer it, a question deserves a real answer._

NeuronQ
  • 7,527
  • 9
  • 42
  • 60
11
import sys

if sys.version_info.major > 2:
    xrange = range

But as Wim implies, this is basically rewriting six yourself.

And as you can see, six does a lot more that handling range. Just e.g. look at the _moved_attributes list in the six source code.

And while Python comes with "batteries included", its standard library is not and cannot be all-encompassing. Nor is it devoid of flaws.

Sometimes there are better batteries out there, and it would be a waste not to use them. Just compare urllib2 with requests. The latter is much nicer to work with.

Roland Smith
  • 42,427
  • 3
  • 64
  • 94
  • I like this approach very much, still trying if it solves all the cases I need to solve.. – alec_djinn May 24 '15 at 12:31
  • Is there anything similar I can use with items and iteritems? – alec_djinn May 29 '15 at 11:14
  • @alec_djinn Yes. It's handled in the six source code. But I won't pretend to know how it works. :-) – Roland Smith May 30 '15 at 10:16
  • 2
    Python.org recommends using **six** sparingly (i.e. only if necessary), see [here](https://wiki.python.org/moin/PortingToPy3k/BilingualQuickRef#Before_you_start). **Future** seems the preferred solution. See [here](https://python-future.org/faq.html#what-is-the-relationship-between-future-and-six) for future vs. six. – Ulrich Stern Jan 20 '19 at 00:24
3

I would recommend writing for py2 or py3 in your projects's modules, but not mix them together and not include any sort of 2/3 checks at all. Your program's logic shouldn't have to care about its version of python, except maybe for avoiding functions on builtin objects that conflict.

Instead, import * from your own compatiblity layer that fixes the differences between your framework and use shadowing to make it transparent to your actual project's module.

For instance, in the compatibility module, you can write Roland Smith's substition for range/xrange, and in your other modules you add "from compatibility import *". Doing this, every module can use "xrange" and the compatibility layer will manage the 2/3 differences.

Unfortunately it won't solve existing objects functions such as dict.iteritems; typically you would monkey-patch the dict methods, but it is not possible on builtin types (see https://stackoverflow.com/a/192857/1741414). I can imagine some workarounds:

  • Function wrappers (essentially sobolevn's answer)
  • Don't use .items() functions at all; use simple loops on keys and then access the dictionary with those keys:
    for key in my_dict:
        value = my_dict[key]
        # rest of code goes here
Community
  • 1
  • 1
Joe
  • 2,496
  • 1
  • 22
  • 30
  • To get rid of items() is probably the best strategy, could solve he problems at the root. Thanks for the suggestion. – alec_djinn May 24 '15 at 12:33
1

I guess you are mixing up array and dict in this case.

If you are restricted in using 3-d party libraries for any reason, so why not like this:

def iterate_items(to_iterate):
    if py >= 3:
        return to_iterate.items()
    else:
        return to_iterate.iteritems()

And then use it:

for item in iterate_items(your_dict):
    ...
sobolevn
  • 16,714
  • 6
  • 62
  • 60
  • Nice approach! However I don't like that I will need to change all the items() with another function call. – alec_djinn May 24 '15 at 11:09
  • Yeah, it feels less than ideal, but I think this solution is significantly less clunky than the key-looping solution when you also need the values, especially within list/dict comprehensions and generators. – David Aug 25 '18 at 19:27
0
import sys
VERSION = float("{}.{}".format(sys.version_info.major, sys.version_info.minor))

And by using this we can write conditional code for desired versions.

if VERSION >= 3.5:
     from subprocess import run as _run
else:
     from subprocess import call as _run
Zudhin
  • 266
  • 2
  • 5