As part of this dichotomy you also need to be careful about opening files. Unless you have been working on Windows, there is a chance you have not always bothered to add the b mode when opening a binary file (e.g., rb for binary reading). Under Python 3, binary files and text files are clearly distinct and mutually incompatible; see the$_{io}$ module for details. Therefore, you must make a decision of whether a file will be used for binary access (allowing binary data to be read and/or written) or textual access (allowing text data to be read and/or written). You should also use io.open() for opening files instead of the built-in open() function as the io module is consistent from Python 2 to 3 while the built-in open() function is not (in Python 3 it’s actually io.open() ). Do not bother with the outdated practice of using codecs.open() as that’s only necessary for keeping compatibility with Python 2.5. The constructors of both str and bytes have different semantics for the same arguments between Python 2 & 3. Passing an integer to bytes in Python 2 will give you the string representation of the integer: bytes(3) == '3' . But in Python 3, an integer argument to$_{bytes}$will give you a bytes object as long as the integer specified, filled with null bytes:$_{bytes(3) == b'\x00\x00\x00'}$ . A similar worry is necessary when passing a bytes object to str . In Python 2 you just get the bytes object back: str(b'3') == b'3' . But in Python 3 you get the string representation of the bytes object: str(b'3') == "b'3'" . Finally, the indexing of binary data requires careful handling (slicing does not require any special handling). In Python 2,$_{b'123'[1] == b'2'}$ while in Python 3$_{b'123'[1] == 50}$ . Because binary data is simply a collection of binary numbers, Python 3 returns the integer value for the byte you index on. But in Python 2 because$_{bytes}$ == str , indexing returns a one-item slice of bytes. The six project has a function named$_{six.indexbytes()}$ which will return an integer like in Python 3: six.indexbytes(b'123', 1) . To summarize: Inevitably you will have code that has to choose what to do based on what version of Python is running. The best way to do this is with feature detection of whether the version of Python you’re running under supports what you need. If for some reason that doesn’t work then you should make the version check be against Python 2 and not Python 3. To help explain this, let’s look at an example. Let’s pretend that you need access to a feature of$_{importlib}$that is available in Python’s standard library since Python 3.3 and available for Python 2 through importlib2 on PyPI. You might be tempted to write code to access e.g. the$_{importlib.abc}$ module by doing the following: The problem with this code is what happens when Python 4 comes out? It would be better to treat Python 2 as the exceptional case instead of Python 3 and assume that future Python versions will be more compatible with Python 3 than Python 2: (continues on next page) 5 import sys if sys.version_info[0] >2: from importlib import abc import sys if sys.version_info[0] ==3: from importlib import abc else : from importlib2import abc 1. Decide which of your APIs take text and which take binary data 2. Make sure that your code that works with text also works with$_{unicode}$ and code for binary data works with $_{bytes}$in Python 2 (see the table above for what methods you cannot use for each type) 3. Mark all binary literals with a$_{b}$ prefix, textual literals with a$_{u}$ prefix 4. Decode binary data to text as soon as possible, encode text as binary data as late as possible 5. Open files using$_{io.open()}$ and make sure to specify the$_{b}$ mode when appropriate 6. Be careful when indexing into binary data Use feature detection instead of version detection