Python 2 and 3 compatibility library from Lyft. Pronounced "Six with a Y".
syx
builds off of the ubiquitous six library, backporting some behavior from Python 3 to Python 2 and includes some helper methods for handling bytes
and str
differences between Python 2 and Python 3.
Python 3 splits up strings and bytes into separate data types. To go from bytes to a string, you must decode. To go from a string to bytes, you must encode. These changes propagated through the standard library. For example, in Python 2 b64encode accepted a string and returned a string, whereas Python 3's b64encode requires a bytes-like object and returns bytes. Dealing with these small changes can be arduous as mentioned during a PyCon 2017 Keynote, so syx
includes two helper methods, ensure_bytes
(aliased as b
) and ensure_str
(aliased as s
). ensure_bytes
converts it's input to bytes
if needed, ensure_str
converts it's input to a string if needed.
For example, say we have a function encode_value_as_b64_str
that accepts a value that could be a string or bytes and returns a base64 encoded string. Using syx
, this function can be written as:
from base64 import b64encode
from syx import b, s
def encode_value_as_b64_str(value):
return s(b64encode(b(value)))
Python 3 changed the default rounding implementation from "Round away from 0" to "Banker's Rounding". As the name implies, banker's rounding is the internationally accepted way of rounding numbers for financial transactions. The main difference is instead of always rounding up during a tie (which introduces a slight skew upwards in your data) banker's rounding rounds towards the nearest even. So, for example, rounding both 1.5
and 2.5
will result in 2
. While this is not what's taught in grade school, it is more accurate.
As a concrete example:
import numpy as np
from syx import round
def bad_round(num):
return int(num + .5)
count = int(1e6)
values = np.random.randint(101, size=count) / 10
real_mean = sum(values) / count
py3_rounded_mean = sum(round(x) for x in values) / count
py2_rounded_mean = sum(bad_round(x) for x in values) / count
print('actual %f' % real_mean)
print('py3 round: %f, error: %f' % (py3_rounded_mean, abs(real_mean - py3_rounded_mean)))
print('py2 round: %f, error: %f' % (py2_rounded_mean, abs(real_mean - py2_rounded_mean)))
actual 4.998499
py3 round: 4.998387, error: 0.000112
py2 round: 5.048665, error: 0.050166
The old rounding method predictably introduces a 1% error upwards. syx
ships an implementation of banker's rounding in Cython so it's as fast or faster than the built in rounding method.
As has been observed by others, hasattr
in Python 2 can dangerously hide bugs. Some would even say that hasattr is Considered Harmful. syx
ships a backport of Python 3's hasattr
behavior.
from syx import hasattr
class GoesBoom(object):
@property
def boom(self):
raise Exception('Oh Noes!')
hasattr(GoesBoom(), 'boom') # Now properly raises an Exception
syx
ships with some lib2to3
fixers to automatically start using the Python 3 backports of round
and hasattr
. You can run these fixers with python-modernize, for example:
python-modernize -w -f syx.fixers.fix_hasattr -f syx.fixers.fix_round ...