Took me way too long to get back to my own topic thanks to the new RC - sorry for that
You can actually skip a ton of these steps - or at least it worked fine on my end to do that... virtualenv
has an argument --python
with which you can specify the Python interpreter with which to create the venv. So it boils down to installing both 2.7 and 3.7 somewhere and then telling virtualenv
which interpreter to use:
virtualenv --python=C:/Python27/python.exe venv27
virtualenv --python=C:/Python37/python.exe venv37
Then just activate
whichever one you need, or - that's how I do it - configure both interpreters in your IDE and then switch there. The only bad thing about the latter is that at least my (slightly outdated) version of PyCharm always needs an extra minute to get back on track after that, and I have to make sure to close all active run/debug panels relating to the old Python environment or it doesn't properly switch
Does that support 2 and 3 concurrently? I always took it to be more a case of "once that's run the code won't run under Py2 anymore", which at least for now would be counter productive.
What I've made good use of is this cheat sheet by the Python-Future project:
https://python-future.org/compatible_idioms.html
And in general it seems to be a good idea to turn on Py3 behaviour as far as possible in your source files through various from __future__
imports. E.g. (from OctoPrint's code base):
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function, unicode_literals
I also have a post-it note attached to my main screen for more than a year now that currently says
except Exception:
-
to_bytes
& to_unicode
io.open
-
list(<generator>)
__metaclass__
Explanation follows
except Exception:
Instead of just except:
for broad catch-all blocks. Not Python 3 specific, but something we switched to in the code base with the Py3 changes.
to_bytes
& to_unicode
Two helper methods built into OctoPrint (octoprint.util
package) that should hopefully make it easier to ensure the expected data type. Will keep their hands off of anything that's already in the correct type.
io.open
Instead of open
if I remember correctly, though I cannot for the life of me right now remember what exactly was special about that. Pretty sure it had something to do with encoding though (because that's the worst about this whole migration).
list(<generator>)
This is what @OutsourcedGuru already mentioned, a ton of the functional programming kinda methods in Python 3 are now returning generators instead of ready data, so if you want to do stuff with that later where it needs to be a list you'll need to convert yourself. Has kicked me in the teeth a couple of times now, one of the fixes that went into RC6 was actually another one of these.
__metaclass__
Meta classes work differently in Py2 (__metaclass__
field) and Py3 (metaclass=
argument in the class declaration). In an incompatible way of course. So yeah, this works best:
from future.utils import with_metaclass
class Foo(with_metaclass(FooType, BarType)):
pass
And then there's this result of searching for except ImportError:
in OctoPrint's code base:
-
Queue
vs queue
:try:
# noinspection PyCompatibility
import queue # Py3
except ImportError:
# noinspection PyCompatibility
import Queue as queue # Py2
-
Chainmap
:try:
# noinspection PyCompatibility
from collections.abc import ChainMap # Py3, built-in
except ImportError:
# noinspection PyCompatibility
from chainmap import Chainmap # Py2, external dependency
-
KeysView
:try:
# noinspection PyCompatibility
from collections.abc import KeysView # Py3
except ImportError:
# noinspection PyCompatibility
from collections import KeysView # Py2
-
scandir
& walk
:try:
# noinspection PyCompatibility
from os import scandir, walk # Py3, built-in
except ImportError:
# noinspection PyCompatibility
from scandir import scandir, walk # Py2, external dependency
-
HTMLParser
try:
# noinspection PyCompatibility
from html.parser import HTMLParser # Py3
except ImportError:
# noinspection PyCompatibility
from HTMLParser import HTMLParser # Py2
-
HTTPResponse
try:
# noinspection PyCompatibility
from http.client import HTTPResponse # Py3
except ImportError:
# noinspection PyCompatibility
from httplib import HTTPResponse # Py2
-
HTTPServer
& BaseHTTPRequestHandler
:try:
# noinspection PyCompatibility
from http.server import HTTPServer, BaseHTTPRequestHandler # Py3
except ImportError:
# noinspection PyCompatibility
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler # Py2
-
urlencode
try:
# noinspection PyCompatibility
from urllib.parse import urlencode # Py3
except ImportError:
# noinspection PyCompatibility
from urllib import urlencode # Py2
-
urllib.unquote
vs urllib.parse.unquote
(same goes for urlencode
):try:
# noinspection PyCompatibility
from urllib.parse import unquote # Py3
except:
# noinspection PyCompatibility
from urllib import unquote # Py2
-
urlparse
try:
# noinspection PyCompatibility
from urllib import parse as urlparse # Py3
except ImportError:
# noinspection PyCompatibility
import urlparse # Py2
-
collections.Iterable
and Python 3.8 (!):try:
from collections import Iterable
except ImportError:
# Python >= 3.8
from collections.abc import Iterable
Now ain't that fun?