""" This module contains backports the data types that were significantly changed in the transition from Python 2 to Python 3. - an implementation of Python 3's bytes object (pure Python subclass of Python 2's builtin 8-bit str type) - an implementation of Python 3's str object (pure Python subclass of Python 2's builtin unicode type) - a backport of the range iterator from Py3 with slicing support It is used as follows:: from __future__ import division, absolute_import, print_function from builtins import bytes, dict, int, range, str to bring in the new semantics for these functions from Python 3. And then, for example:: b = bytes(b'ABCD') assert list(b) == [65, 66, 67, 68] assert repr(b) == "b'ABCD'" assert [65, 66] in b # These raise TypeErrors: # b + u'EFGH' # b.split(u'B') # bytes(b',').join([u'Fred', u'Bill']) s = str(u'ABCD') # These raise TypeErrors: # s.join([b'Fred', b'Bill']) # s.startswith(b'A') # b'B' in s # s.find(b'A') # s.replace(u'A', b'a') # This raises an AttributeError: # s.decode('utf-8') assert repr(s) == 'ABCD' # consistent repr with Py3 (no u prefix) for i in range(10**11)[:10]: pass and:: class VerboseList(list): def append(self, item): print('Adding an item') super().append(item) # new simpler super() function For more information: --------------------- - future.types.newbytes - future.types.newdict - future.types.newint - future.types.newobject - future.types.newrange - future.types.newstr Notes ===== range() ------- ``range`` is a custom class that backports the slicing behaviour from Python 3 (based on the ``xrange`` module by Dan Crosta). See the ``newrange`` module docstring for more details. super() ------- ``super()`` is based on Ryan Kelly's ``magicsuper`` module. See the ``newsuper`` module docstring for more details. round() ------- Python 3 modifies the behaviour of ``round()`` to use "Banker's Rounding". See http://stackoverflow.com/a/10825998. See the ``newround`` module docstring for more details. """ from __future__ import absolute_import, division, print_function import functools from numbers import Integral from future import utils # Some utility functions to enforce strict type-separation of unicode str and # bytes: def disallow_types(argnums, disallowed_types): """ A decorator that raises a TypeError if any of the given numbered arguments is of the corresponding given type (e.g. bytes or unicode string). For example: @disallow_types([0, 1], [unicode, bytes]) def f(a, b): pass raises a TypeError when f is called if a unicode object is passed as `a` or a bytes object is passed as `b`. This also skips over keyword arguments, so @disallow_types([0, 1], [unicode, bytes]) def g(a, b=None): pass doesn't raise an exception if g is called with only one argument a, e.g.: g(b'Byte string') Example use: >>> class newbytes(object): ... @disallow_types([1], [unicode]) ... def __add__(self, other): ... pass >>> newbytes('1234') + u'1234' #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... TypeError: can't concat 'bytes' to (unicode) str """ def decorator(function): @functools.wraps(function) def wrapper(*args, **kwargs): # These imports are just for this decorator, and are defined here # to prevent circular imports: from .newbytes import newbytes from .newint import newint from .newstr import newstr errmsg = "argument can't be {0}" for (argnum, mytype) in zip(argnums, disallowed_types): # Handle the case where the type is passed as a string like 'newbytes'. if isinstance(mytype, str) or isinstance(mytype, bytes): mytype = locals()[mytype] # Only restrict kw args only if they are passed: if len(args) <= argnum: break # Here we use type() rather than isinstance() because # __instancecheck__ is being overridden. E.g. # isinstance(b'abc', newbytes) is True on Py2. if type(args[argnum]) == mytype: raise TypeError(errmsg.format(mytype)) return function(*args, **kwargs) return wrapper return decorator def no(mytype, argnums=(1,)): """ A shortcut for the disallow_types decorator that disallows only one type (in any position in argnums). Example use: >>> class newstr(object): ... @no('bytes') ... def __add__(self, other): ... pass >>> newstr(u'1234') + b'1234' #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... TypeError: argument can't be bytes The object can also be passed directly, but passing the string helps to prevent circular import problems. """ if isinstance(argnums, Integral): argnums = (argnums,) disallowed_types = [mytype] * len(argnums) return disallow_types(argnums, disallowed_types) def issubset(list1, list2): """ Examples: >>> issubset([], [65, 66, 67]) True >>> issubset([65], [65, 66, 67]) True >>> issubset([65, 66], [65, 66, 67]) True >>> issubset([65, 67], [65, 66, 67]) False """ n = len(list1) for startpos in range(len(list2) - n + 1): if list2[startpos:startpos+n] == list1: return True return False if utils.PY3: import builtins bytes = builtins.bytes dict = builtins.dict int = builtins.int list = builtins.list object = builtins.object range = builtins.range str = builtins.str # The identity mapping newtypes = {bytes: bytes, dict: dict, int: int, list: list, object: object, range: range, str: str} __all__ = ['newtypes'] else: from .newbytes import newbytes from .newdict import newdict from .newint import newint from .newlist import newlist from .newrange import newrange from .newobject import newobject from .newstr import newstr newtypes = {bytes: newbytes, dict: newdict, int: newint, long: newint, list: newlist, object: newobject, range: newrange, str: newbytes, unicode: newstr} __all__ = ['newbytes', 'newdict', 'newint', 'newlist', 'newrange', 'newstr', 'newtypes']