Forward-compatible print statement in python 2.5 - python

OK, maybe I'm just having an off day. This seems like something a lot of people must be asking, but Google is failing me horribly. The closest thing I found was this which doesn't exactly address this issue.
At work I run Arch on my desktop (which is python 3 by default), and Debian Lenny on my company's servers (which is python 2.5). I want to write a single python script that will work in both python 2 and 3. It's a very simple script, not much to it (mostly it just calls off to git using subprocess). Everything already works in both versions of python EXCEPT for the damned print statements.
Everyone out there seems to suggest the from __future__ import print_function trick. However this was introduced in python 2.6, and I'm stuck with 2.5.
So what are my options? How can I call print in both 2.5 and 3 using the same script? I was thinking maybe some kind of wrapper function, but this might not be the most "pythonic" way of doing things. Your thoughts? And no, upgrading the server to 2.6 isn't an option.
Thanks!

print("hi") works on both py 2 and 3 without from __future__ in py 2.5
alternatively, although not recommended:
import sys
sys.stdout.write("hi")

Why don't you just use the logging framework? It mitigates your issue, and is much better than print statements littered throughout the code.

This works for me:
import sys
if sys.version_info[0] == 2:
def print_(*args):
w = sys.stdout.write
w( ', '.join(str(a) for a in args) )
w( '\n' )
else:
print_ = getattr(__builtins__, 'print')
If you need the full featured print functionality, you're better off using the print_ from six. In this case,
from six import print_
// replace all "print ..." by "print_(...)"

Related

How can I avoid this Python catch-22 of version testing -vs- initial syntax checker

I have a python script that requires Python 3.8 or better to support the walrus operator and other Python3 operations. I want to test the version and output a "nice" message if the minimum version is not detected, however, I am getting the following syntax checking error if I am on Python2 and the script will not run to give the "nice" message.
File "./te_add_for_wcs.py", line 743
if (cert_count := apiResponse.get("X-Total-Count","NO_COUNT")) == 'NO_COUNT':
^
SyntaxError: invalid syntax
Is there a way to get around this, or am I out of luck when users of Python2 attempt to use my script and would need to figure out the error means wrong version?
Trying to keep this to just one script file, as I can think of ways use multiple scripts that call each other to take care of prerequisites.
I appreciate all the "comment" answers!
#MattDMo answer is what I will need to do, as I have no interest a ton of extra work (as indicated by #chepner), because #Kraigolas is absolutely correct - version 2 should not be used by anyone in production, or only used in isolated environments.
Developers like myself should assume that people will be using version 3 of Python and I should be documenting it well that scripts will only support Python3 and have logic that detects the minimum version of Python3 that is required.
Thank you again!
The problem is that := is a syntax error, raised before any portion of the script can execute. You could "hide" the use of := in a module that is imported conditionally, based on a version check.
if sys.version_info.major < 3 or sys.version_info.minor < 8:
sys.exit("Script requires Python 3.8 or later")
else:
import module.with_function_using_assignment_expression
Rather than an error, though, I would just hold off using := in the code, but adding a deprecation warning to the script to let users know that a future version of the script (with :=) will require Python 3.8 or later.
import warnings
# Or maybe DecprecationWarning; I'm not entirely clear on the
# distinction between the two.
warnings.warn("This script will require Python 3.8 or later in the near future",
warnings.FutureWarning)
...
# TODO: use an assignment expression to define cert_count directly
# in the if condition.
cert_count = apiResponse.get("X-Total-Count", "NO_COUNT")
if cert_count == "NO_COUNT":
...
After a suitable waiting period, you can go ahead and use :=, and let the users accept the consequences of not upgrading to Python 3.8.
While I understand wanting to keep this in one file, for completeness, here's an easy 2-file solution:
wrapper_main.py
import sys
if sys.version_info.major < 3: # replace with your exact version requirements
print("Please upgrade to python3!")
sys.exit(0)
import main
main.main()
main.py
def main():
a := 1
print("Entering main!")
Tested on python 2.7 that this runs and exits without syntax error.

Best way to write Python 2 and 3 compatible code using nothing but the standard library

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.
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:
http://python-future.org/compatible_idioms.html
http://lucumr.pocoo.org/2011/1/22/forwards-compatible-python/
https://wiki.python.org/moin/PortingToPy3k/BilingualQuickRef
(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._
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.
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
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):
...
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

How can I tell new Python to use the old print

The print function's syntax has been changed in newer versions of python. The problem is that at home, I have the newer version of python while at office the old one. How can I have the same program run on both the newer and old python versions?
If you're using iPython/ Jupyter, run
%autocall 1
After that, you can use
print "yo"
like in Python 2.
Many 2 vs 3 porting issues are covered well here.
In general, I think it's more hassle than it's worth to try to keep a single .py file that runs under both versions. But if you want to try, that link shows usable workarounds.
While it's true that Python 2 supports, for example,
print("abc", 3)
the results aren't the same: in Python 2 that prints the tuple ('abc', 3). And Python 2's print doesn't support Python 3's optional keyword arguments at all (like file=).
You can use new print syntax on older version of python.
You can use the new print function in old scripts like this:
# This line goes at the top of the file ...
from __future__ import print_function
# And then you can use the new-style print function anywhere
print("hello")

Backwards-compatible input calls in Python

I was wondering if anyone has suggestions for writing a backwards-compatible input() call for retrieving a filepath?
In Python 2.x, raw_input worked fine for input like /path/to/file. Using input works fine in this case for 3.x, but complains in 2.x because of the eval behavior.
One solution is to check the version of Python and, based on the version, map either input or raw_input to a new function:
if sys.version_info[0] >= 3:
get_input = input
else:
get_input = raw_input
I'm sure there is a better way to do this though. Anyone have any suggestions?
Since the Python 2.x version of input() is essentially useless, you can simply overwrite it by raw_input:
try:
input = raw_input
except NameError:
pass
In general, I would not try to aim at code that works with both, Python 2.x and 3.x, but rather write your code in a way that it works on 2.x and you get a working 3.x version by using the 2to3 script.
This code is taught in many Python education and training programs now.
Usually taught together:
from __future__ import print_function
if hasattr(__builtins__, 'raw_input'):
input = raw_input
First line: imports the Python 3.x print() function into Python 2.7 so print() behaves the same under both versions of Python. If this breaks your code due to older print "some content" calls, you can leave this line off.
Second and third lines: sets Python 2.7 raw_input() to input() so input() can be used under both versions of Python without problems. This can be used all by itself if this is the only compatibility fix you wish to include in your code.
There are more from __future__ imports available on the Python.org site for other language compatibility issues. There is also a library called "six" that can be looked up for compatibility solutions when dealing with other issues.
The way you are handling it is just fine. There are probably more similar ways using the sys module, but just in keep in mind that if you are program is doing something more than trivial with strings and files, it is better to have two versions of your program instead of having a backwards compatible python3 program.
You could import the function:
from builtins import input
Unfortunately though this method requires an external dependency via pip install future

How to write a Python 2.6+ script that gracefully fails with older Python?

I'm using the new print from Python 3.x and I observed that the following code does not compile due to the end=' '.
from __future__ import print_function
import sys
if sys.hexversion < 0x02060000:
raise Exception("py too old")
...
print("x",end=" ") # fails to compile with py24
How can I continue using the new syntax but make the script fails nicely? Is it mandatory to call another script and use only safe syntax in this one?
The easy method for Python 2.6 is just to add a line like:
b'You need Python 2.6 or later.'
at the start of the file. This exploits the fact that byte literals were introduced in 2.6 and so any earlier versions will raise a SyntaxError with whatever message you write given as the stack trace.
There are some suggestions in this question here, but it looks like it is not easily possible. You'll have to create a wrapper script.
One way is to write your module using python 2.x print statement, then when you want to port it into python 3, you use 2to3 script. I think there are scripts for 3to2 conversion as well, although they seems to be less mature than 2to3.
Either way, in biggers scripts, you should always separate domain logic and input/output; that way, all the print statements/functions are bunched up together in a single file. For logging, you should use the logging module.

Categories