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:
- 
Queuevsqueue: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
 
- 
HTMLParsertry:
    # noinspection PyCompatibility
    from html.parser import HTMLParser # Py3
except ImportError:
    # noinspection PyCompatibility
    from HTMLParser import HTMLParser # Py2
 
- 
HTTPResponsetry:
    # 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
 
- 
urlencodetry:
    # noinspection PyCompatibility
    from urllib.parse import urlencode # Py3
except ImportError:
    # noinspection PyCompatibility
    from urllib import urlencode # Py2
 
- 
urllib.unquotevsurllib.parse.unquote(same goes forurlencode):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.Iterableand Python 3.8 (!):try:
    from collections import Iterable
except ImportError:
    # Python >= 3.8
    from collections.abc import Iterable
 
Now ain't that fun?