diff --git a/.travis.yml b/.travis.yml
index 9c6bfa121..5ad1b61ae 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,7 +5,6 @@ dist: xenial # required for Python >= 3.7
language: python
python:
- - "2.7"
- "3.6"
- "3.7"
@@ -17,20 +16,12 @@ before_install:
# Install Miniconda
# We do this conditionally because it saves us some downloading if the
# version is the same.
- - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then
- wget https://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh;
- else
- wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh;
- fi
+ - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh;
- bash miniconda.sh -b -p $HOME/miniconda
- export PATH="$HOME/miniconda/bin:$PATH"
- hash -r
- conda config --add channels conda-forge
- conda config --set always_yes yes --set changeps1 no
- # workaround for conda >= 4.8
- - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then
- pip install tqdm;
- fi
- conda update -q conda
# Useful for debugging any issues with conda
diff --git a/README.rst b/README.rst
index f15967fa7..8c04ecf96 100644
--- a/README.rst
+++ b/README.rst
@@ -73,7 +73,7 @@ Once you have satisfied the requirements detailed below, simply run::
Required Dependencies
---------------------
-- Python 2.7, 3.6 or 3.7
+- Python 3.6 or 3.7
- `numpy `__ (1.13 or later)
- `pandas `__ (0.20 or later)
diff --git a/condarecipe/larray/conda_build_config.yaml b/condarecipe/larray/conda_build_config.yaml
index e59e77303..a328eb668 100644
--- a/condarecipe/larray/conda_build_config.yaml
+++ b/condarecipe/larray/conda_build_config.yaml
@@ -1,5 +1,3 @@
python:
- - 2.7
- - 3.5
- 3.6
- 3.7
diff --git a/doc/source/changes/version_0_33.rst.inc b/doc/source/changes/version_0_33.rst.inc
index 522358af8..be8409d99 100644
--- a/doc/source/changes/version_0_33.rst.inc
+++ b/doc/source/changes/version_0_33.rst.inc
@@ -12,7 +12,7 @@ Syntax changes
Backward incompatible changes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-* other backward incompatible changes
+* dropped support for Python 2 (closes :issue:`567`).
New features
diff --git a/doc/source/contribute.rst b/doc/source/contribute.rst
index 735eea788..636c2148a 100644
--- a/doc/source/contribute.rst
+++ b/doc/source/contribute.rst
@@ -181,9 +181,6 @@ code conventions. Among others, this means:
This summary should not prevent you from reading the PEP!
-LArray is currently compatible with both Python 2 and 3.
-So make sure your code is compatible with both versions.
-
Step 3: Document your code
~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/larray/__init__.py b/larray/__init__.py
index 3006e6bc8..f02766cec 100644
--- a/larray/__init__.py
+++ b/larray/__init__.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, division, print_function
-
__version__ = '0.33-dev'
diff --git a/larray/core/abstractbases.py b/larray/core/abstractbases.py
index 599e74975..07e80e57b 100644
--- a/larray/core/abstractbases.py
+++ b/larray/core/abstractbases.py
@@ -1,18 +1,15 @@
-from __future__ import absolute_import
-
-from abc import ABCMeta
+from abc import ABC
# define abstract base classes to enable isinstance type checking on our objects
# idea taken from https://github.com/pandas-dev/pandas/blob/master/pandas/core/dtypes/generic.py
-# FIXME: __metaclass__ is ignored in Python 3
-class ABCAxis(object):
- __metaclass__ = ABCMeta
+class ABCAxis(ABC):
+ pass
class ABCAxisReference(ABCAxis):
- __metaclass__ = ABCMeta
+ pass
-class ABCArray(object):
- __metaclass__ = ABCMeta
+class ABCArray(ABC):
+ pass
diff --git a/larray/core/array.py b/larray/core/array.py
index 103f6d869..9881779e5 100644
--- a/larray/core/array.py
+++ b/larray/core/array.py
@@ -1,6 +1,3 @@
-# -*- coding: utf8 -*-
-from __future__ import absolute_import, division, print_function
-
"""
Array class
"""
@@ -30,6 +27,8 @@
from collections import OrderedDict
from itertools import product, chain, groupby, islice, repeat
+from collections.abc import Iterable, Sequence
+import builtins
import os
import sys
import functools
@@ -59,7 +58,6 @@
float_error_handler_factory, light_product, common_type,
renamed_to, deprecate_kwarg, LHDFStore, lazy_attribute, unique_multi, SequenceZip,
Repeater, Product, ensure_no_numpy_type)
-from larray.util.compat import PY2, basestring, Iterable, Sequence, builtins
from larray.util.options import _OPTIONS, DISPLAY_MAXLINES, DISPLAY_EDGEITEMS, DISPLAY_WIDTH, DISPLAY_PRECISION
@@ -305,42 +303,23 @@ def concat(arrays, axis=0, dtype=None):
return result
-if PY2:
- class ArrayIterator(object):
- __slots__ = ('next',)
-
- def __init__(self, array):
- data_iter = iter(array.data)
- next_data_func = data_iter.next
- res_axes = array.axes[1:]
- # this case should not happen (handled by the fastpath in Array.__iter__)
- assert len(res_axes) > 0
-
- def next_func():
- return Array(next_data_func(), res_axes)
+class ArrayIterator(object):
+ __slots__ = ('__next__',)
- self.next = next_func
-
- def __iter__(self):
- return self
-else:
- class ArrayIterator(object):
- __slots__ = ('__next__',)
-
- def __init__(self, array):
- data_iter = iter(array.data)
- next_data_func = data_iter.__next__
- res_axes = array.axes[1:]
- # this case should not happen (handled by the fastpath in Array.__iter__)
- assert len(res_axes) > 0
+ def __init__(self, array):
+ data_iter = iter(array.data)
+ next_data_func = data_iter.__next__
+ res_axes = array.axes[1:]
+ # this case should not happen (handled by the fastpath in Array.__iter__)
+ assert len(res_axes) > 0
- def next_func():
- return Array(next_data_func(), res_axes)
+ def next_func():
+ return Array(next_data_func(), res_axes)
- self.__next__ = next_func
+ self.__next__ = next_func
- def __iter__(self):
- return self
+ def __iter__(self):
+ return self
# TODO: rename to ArrayIndexIndexer or something like that
@@ -826,7 +805,7 @@ def title(self):
def title(self, title):
import warnings
warnings.warn("title attribute is deprecated. Please use meta.title instead", FutureWarning, stacklevel=2)
- if not isinstance(title, basestring):
+ if not isinstance(title, str):
raise TypeError("Expected string value, got {}".format(type(title).__name__))
self._meta.title = title
@@ -1411,8 +1390,6 @@ def __array_wrap__(self, out_arr, context=None):
def __bool__(self):
return bool(self.data)
- # Python 2
- __nonzero__ = __bool__
# TODO: either support a list (of axes names) as first argument here (and set_labels)
# or don't support that in set_axes
@@ -1591,7 +1568,7 @@ def reindex(self, axes_to_reindex=None, new_axis=None, fill_value=nan, inplace=F
a1 c0 2 1
"""
# XXX: can't we move this to AxisCollection.replace?
- if isinstance(axes_to_reindex, basestring) and '=' in axes_to_reindex:
+ if isinstance(axes_to_reindex, str) and '=' in axes_to_reindex:
axes_to_reindex = Axis(axes_to_reindex)
if isinstance(axes_to_reindex, (Group, Axis)) and not isinstance(axes_to_reindex, AxisReference):
new_axis = axes_to_reindex if isinstance(axes_to_reindex, Axis) else Axis(axes_to_reindex)
@@ -2820,7 +2797,7 @@ def to_labelgroup(key, stack_depth=1):
if not all(g.axis.equals(axis) for g in groups[1:]):
raise ValueError("group with different axes: %s" % str(key))
return groups
- if isinstance(key, (Group, int, basestring, list, slice)):
+ if isinstance(key, (Group, int, str, list, slice)):
return self.axes._guess_axis(key)
else:
raise NotImplementedError("%s has invalid type (%s) for a group aggregate key"
@@ -7744,10 +7721,10 @@ def split_axes(self, axes=None, sep='_', names=None, regex=None, sort=False, fil
# * somehow factorize this code with AxisCollection.split_axes
if axes is None:
axes = {axis: None for axis in array.axes if axis.name is not None and sep in axis.name}
- elif isinstance(axes, (int, basestring, Axis)):
+ elif isinstance(axes, (int, str, Axis)):
axes = {axes: names}
elif isinstance(axes, (list, tuple)):
- if all(isinstance(axis, (int, basestring, Axis)) for axis in axes):
+ if all(isinstance(axis, (int, str, Axis)) for axis in axes):
axes = {axis: None for axis in axes}
else:
raise ValueError("Expected tuple or list of int, string or Axis instances")
@@ -9288,12 +9265,12 @@ def stack(elements=None, axes=None, title=None, meta=None, dtype=None, res_axes=
if elements is not None and kwargs:
raise TypeError("stack() accepts either keyword arguments OR a collection of elements, not both")
- if isinstance(axes, basestring) and '=' in axes:
+ if isinstance(axes, str) and '=' in axes:
axes = Axis(axes)
elif isinstance(axes, Group):
axes = Axis(axes)
- if axes is not None and not isinstance(axes, basestring):
+ if axes is not None and not isinstance(axes, str):
axes = AxisCollection(axes)
if kwargs:
@@ -9321,7 +9298,7 @@ def stack(elements=None, axes=None, title=None, meta=None, dtype=None, res_axes=
if all(isinstance(e, tuple) for e in elements):
assert all(len(e) == 2 for e in elements)
- if axes is None or isinstance(axes, basestring):
+ if axes is None or isinstance(axes, str):
keys = [k for k, v in elements]
values = [v for k, v in elements]
# assert that all keys are indexers
@@ -9338,7 +9315,7 @@ def translate_and_sort_key(key, axes):
dict_elements = {translate_and_sort_key(key, axes): value for key, value in elements}
items = [(k, dict_elements[k]) for k in axes.iter_labels()]
else:
- if axes is None or isinstance(axes, basestring):
+ if axes is None or isinstance(axes, str):
axes = AxisCollection(Axis(len(elements), axes))
else:
# TODO: add support for more than one axis here
@@ -9568,7 +9545,7 @@ def values_with_expand(value, axes, readonly=True, ascending=True):
if not isinstance(axes, (tuple, list, AxisCollection)):
axes = (axes,)
# transform string axes definitions to objects
- axes = [Axis(axis) if isinstance(axis, basestring) and '=' in axis else axis
+ axes = [Axis(axis) if isinstance(axis, str) and '=' in axis else axis
for axis in axes]
# transform string axes references to objects
axes = AxisCollection([axis if isinstance(axis, Axis) else all_axes[axis]
diff --git a/larray/core/axis.py b/larray/core/axis.py
index c203e4e7d..404415a66 100644
--- a/larray/core/axis.py
+++ b/larray/core/axis.py
@@ -1,6 +1,3 @@
-# -*- coding: utf8 -*-
-from __future__ import absolute_import, division, print_function
-
import fnmatch
import re
import sys
@@ -17,7 +14,6 @@
from larray.util.oset import *
from larray.util.misc import (duplicates, array_lookup2, ReprString, index_by_id, renamed_to, common_type, LHDFStore,
lazy_attribute, _isnoneslice, unique_multi, Product)
-from larray.util.compat import (basestring, PY2, unicode, long)
np_frompyfunc = np.frompyfunc
@@ -87,7 +83,7 @@ def __init__(self, labels, name=None):
name = labels.axis
if isinstance(name, Axis):
name = name.name
- if isinstance(labels, basestring):
+ if isinstance(labels, str):
if '=' in labels:
name, labels = [o.strip() for o in labels.split('=')]
elif '..' not in labels and ',' not in labels:
@@ -98,10 +94,10 @@ def __init__(self, labels, name=None):
# make sure we do not have np.str_ as it causes problems down the
# line with xlwings. Cannot use isinstance to check that though.
- name_is_python_str = type(name) is unicode or type(name) is bytes
- if isinstance(name, basestring) and not name_is_python_str:
- name = unicode(name)
- if name is not None and not isinstance(name, (int, basestring)):
+ name_is_python_str = type(name) is str or type(name) is bytes
+ if isinstance(name, str) and not name_is_python_str:
+ name = str(name)
+ if name is not None and not isinstance(name, (int, str)):
raise TypeError("Axis name should be None, int or str but is: %s (%s)" % (name, type(name).__name__))
self.name = name
self._labels = None
@@ -189,7 +185,7 @@ def labels(self):
def labels(self, labels):
if labels is None:
raise TypeError("labels should be a sequence or a single int, not None")
- if isinstance(labels, (int, long, np.integer)):
+ if isinstance(labels, (int, np.integer)):
length = labels
labels = np.arange(length)
iswildcard = True
@@ -783,34 +779,11 @@ def __contains__(self, key):
def __hash__(self):
return id(self)
- # needed for Python 2 only (on Python2 all classes defining __slots__ need to define __getstate__/__setstate__ too)
- def __getstate__(self):
- return self.name, self._length if self._iswildcard else self._labels
-
- # needed for Python 2 only
- def __setstate__(self, state):
- name, labels = state
- self.name = name
- self._labels = None
- self._length = None
- self._iswildcard = False
-
- self.__mapping = None
- self.__sorted_keys = None
- self.__sorted_values = None
- # set _labels, _length and _iswildcard via the property
- self.labels = labels
-
def _is_key_type_compatible(self, key):
key_kind = np.dtype(type(key)).kind
label_kind = self.labels.dtype.kind
- # on Python2, ascii-only unicode string can match byte strings (and vice versa), so we shouldn't be more picky
- # here than dict hashing
- str_key = key_kind in ('S', 'U')
- str_label = label_kind in ('S', 'U')
- py2_str_match = PY2 and str_key and str_label
# object kind can match anything
- return key_kind == label_kind or key_kind == 'O' or label_kind == 'O' or py2_str_match
+ return key_kind == label_kind or key_kind == 'O' or label_kind == 'O'
def index(self, key):
r"""
@@ -853,7 +826,7 @@ def index(self, key):
except (KeyError, TypeError, IndexError):
pass
- if isinstance(key, basestring):
+ if isinstance(key, str):
# try the key as-is to allow getting at ticks with special characters (",", ":", ...)
try:
# avoid matching 0 against False or 0.0, note that Group keys have object dtype and so always pass this
@@ -868,11 +841,11 @@ def index(self, key):
# transform "specially formatted strings" for slices, lists, LGroup and IGroup to actual objects
key = _to_key(key)
- if not PY2 and isinstance(key, range):
+ if isinstance(key, range):
key = list(key)
# this can happen when key was passed as a string and converted to a Group via _to_key
- if isinstance(key, Group) and isinstance(key.axis, basestring) and key.axis != self.name:
+ if isinstance(key, Group) and isinstance(key.axis, str) and key.axis != self.name:
raise KeyError(key)
if isinstance(key, IGroup):
@@ -1168,7 +1141,7 @@ def union(self, other):
>>> a.union(['a1', 'a2', 'a3'])
Axis(['a0', 'a1', 'a2', 'a3'], 'a')
"""
- if isinstance(other, basestring):
+ if isinstance(other, str):
# TODO : remove [other] if ... when FuturWarning raised in Axis.init will be removed
other = _to_ticks(other, parse_single_int=True) if '..' in other or ',' in other else [other]
if isinstance(other, Axis):
@@ -1204,7 +1177,7 @@ def intersection(self, other):
>>> a.intersection(['a1', 'a2', 'a3'])
Axis(['a1', 'a2'], 'a')
"""
- if isinstance(other, basestring):
+ if isinstance(other, str):
# TODO : remove [other] if ... when FuturWarning raised in Axis.init will be removed
other = _to_ticks(other, parse_single_int=True) if '..' in other or ',' in other else [other]
if isinstance(other, Axis):
@@ -1241,7 +1214,7 @@ def difference(self, other):
>>> a.difference(['a1', 'a2', 'a3'])
Axis(['a0'], 'a')
"""
- if isinstance(other, basestring):
+ if isinstance(other, str):
# TODO : remove [other] if ... when FuturWarning raised in Axis.init will be removed
other = _to_ticks(other, parse_single_int=True) if '..' in other or ',' in other else [other]
if isinstance(other, Axis):
@@ -1441,7 +1414,7 @@ class AxisCollection(object):
def __init__(self, axes=None):
if axes is None:
axes = []
- elif isinstance(axes, (int, long, Group, Axis)):
+ elif isinstance(axes, (int, Group, Axis)):
axes = [axes]
elif isinstance(axes, str):
axes = [axis.strip() for axis in axes.split(';')]
@@ -1600,7 +1573,7 @@ def __getitem__(self, key):
elif key is None:
raise KeyError("axis '%s' not found in %s" % (key, self))
else:
- assert isinstance(key, basestring), type(key)
+ assert isinstance(key, str), type(key)
if key in self._map:
return self._map[key]
else:
@@ -1792,7 +1765,7 @@ def isaxis(self, value):
# we could also provide an explicit kwarg (ie this would effectively forbid having an axis named "axis").
# arr.sum(axis=0). I think this is the sanest option. The error message in case we use it without the
# keyword needs to be clearer though.
- return isinstance(value, Axis) or (isinstance(value, basestring) and value in self)
+ return isinstance(value, Axis) or (isinstance(value, str) and value in self)
# 2) slightly inconsistent API: allow aggregate over single labels if they are string, but not int
# arr.sum(0) would sum on the first axis, but arr.sum('M') would
# sum a single tick. I don't like this option.
@@ -2303,7 +2276,7 @@ def replace(self, axes_to_replace=None, new_axis=None, inplace=False, **kwargs):
Axis(['c0', 'c1', 'c2'], 'column')
])
"""
- if not PY2 and isinstance(axes_to_replace, zip):
+ if isinstance(axes_to_replace, zip):
axes_to_replace = list(axes_to_replace)
if isinstance(axes_to_replace, (list, AxisCollection)) and \
@@ -2318,7 +2291,7 @@ def replace(self, axes_to_replace=None, new_axis=None, inplace=False, **kwargs):
elif isinstance(axes_to_replace, list):
assert all([isinstance(item, tuple) and len(item) == 2 for item in axes_to_replace])
items = axes_to_replace[:]
- elif isinstance(axes_to_replace, (basestring, Axis, int)):
+ elif isinstance(axes_to_replace, (str, Axis, int)):
items = [(axes_to_replace, new_axis)]
else:
items = []
@@ -2469,7 +2442,7 @@ def set_labels(self, axis=None, labels=None, inplace=False, **kwargs):
changes = {}
elif isinstance(axis, dict):
changes = axis
- elif isinstance(axis, (basestring, Axis, int)):
+ elif isinstance(axis, (str, Axis, int)):
changes = {axis: labels}
else:
raise ValueError("Expected None or a string/int/Axis/dict instance for axis argument")
@@ -2542,7 +2515,7 @@ def __sub__(self, axes):
--------
without
"""
- if isinstance(axes, basestring):
+ if isinstance(axes, str):
axes = axes.split(',')
elif isinstance(axes, (int, Axis)):
axes = [axes]
@@ -2648,7 +2621,7 @@ def _translate_axis_key(self, axis_key):
# Need to convert string keys to groups otherwise command like
# >>> ndtest((5, 5)).drop('1[a0]')
# will work although it shouldn't
- if isinstance(axis_key, basestring):
+ if isinstance(axis_key, str):
key = _to_key(axis_key)
if isinstance(key, Group):
axis_key = key
@@ -2830,7 +2803,7 @@ def _key_to_raw_and_axes(self, key, collapse_slices=False, translate_key=True):
# transform non-Array advanced keys (list and ndarray) to Array
def to_la_ikey(axis, axis_key):
- if isinstance(axis_key, (int, long, np.integer, slice, Array)):
+ if isinstance(axis_key, (int, np.integer, slice, Array)):
return axis_key
else:
assert isinstance(axis_key, (list, np.ndarray))
@@ -3307,10 +3280,10 @@ def split_axes(self, axes=None, sep='_', names=None, regex=None):
"""
if axes is None:
axes = {axis: None for axis in self if sep in axis.name}
- elif isinstance(axes, (int, basestring, Axis)):
+ elif isinstance(axes, (int, str, Axis)):
axes = {axes: None}
elif isinstance(axes, (list, tuple)):
- if all(isinstance(axis, (int, basestring, Axis)) for axis in axes):
+ if all(isinstance(axis, (int, str, Axis)) for axis in axes):
axes = {axis: None for axis in axes}
else:
raise ValueError("Expected tuple or list of int, string or Axis instances")
diff --git a/larray/core/constants.py b/larray/core/constants.py
index 503865f48..d41f80b6d 100644
--- a/larray/core/constants.py
+++ b/larray/core/constants.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, division, print_function
-
import numpy as np
diff --git a/larray/core/expr.py b/larray/core/expr.py
index 07c55e7e4..bdc48bcd3 100644
--- a/larray/core/expr.py
+++ b/larray/core/expr.py
@@ -1,6 +1,3 @@
-# -*- coding: utf8 -*-
-from __future__ import absolute_import, division, print_function
-
import sys
diff --git a/larray/core/group.py b/larray/core/group.py
index e3134036e..d7cdcada7 100644
--- a/larray/core/group.py
+++ b/larray/core/group.py
@@ -1,6 +1,3 @@
-# -*- coding: utf8 -*-
-from __future__ import absolute_import, division, print_function
-
import fnmatch
import re
import sys
@@ -13,7 +10,6 @@
from larray.core.abstractbases import ABCAxis, ABCAxisReference, ABCArray
from larray.util.oset import *
from larray.util.misc import (unique, find_closing_chr, _parse_bound, _seq_summary, _isintstring, renamed_to, LHDFStore)
-from larray.util.compat import basestring, PY2
def _slice_to_str(key, repr_func=str):
@@ -239,7 +235,7 @@ def _range_str_to_range(s, stack_depth=1):
if stop is None:
raise ValueError("no stop bound provided in range: %r" % s)
stop = _parse_bound(stop, stack_depth + 1)
- if isinstance(start, basestring) or isinstance(stop, basestring):
+ if isinstance(start, str) or isinstance(stop, str):
start, stop = str(start), str(stop)
# TODO: use parse_bound
step = int(step) if step is not None else 1
@@ -372,7 +368,6 @@ def _to_tick(v):
return str(v)
-# TODO: remove the conversion to list in doctests once Python 2 is dropped
def _to_ticks(s, parse_single_int=False):
r"""
Makes a (list of) value(s) usable as the collection of labels for an Axis (ie hashable).
@@ -390,18 +385,18 @@ def _to_ticks(s, parse_single_int=False):
Examples
--------
- >>> list(_to_ticks('M , F'))
- ['M', 'F']
- >>> list(_to_ticks('A,C..E,F..G,Z'))
- ['A', 'C', 'D', 'E', 'F', 'G', 'Z']
- >>> list(_to_ticks('U'))
- ['U']
- >>> list(_to_ticks('..3'))
- [0, 1, 2, 3]
- >>> list(_to_ticks('01..12'))
- ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12']
- >>> list(_to_ticks('01,02,03,10,11,12'))
- ['01', '02', '03', '10', '11', '12']
+ >>> _to_ticks('M , F') # doctest: +NORMALIZE_WHITESPACE
+ array(['M', 'F'], dtype='>> _to_ticks('A,C..E,F..G,Z') # doctest: +NORMALIZE_WHITESPACE
+ array(['A', 'C', 'D', 'E', 'F', 'G', 'Z'], dtype='>> _to_ticks('U') # doctest: +NORMALIZE_WHITESPACE
+ array(['U'], dtype='>> _to_ticks('..3') # doctest: +NORMALIZE_WHITESPACE
+ array([0, 1, 2, 3])
+ >>> _to_ticks('01..12') # doctest: +NORMALIZE_WHITESPACE
+ array(['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'], dtype='>> _to_ticks('01,02,03,10,11,12') # doctest: +NORMALIZE_WHITESPACE
+ array(['01', '02', '03', '10', '11', '12'], dtype='= '3' and isinstance(s, range):
ticks = s
- elif isinstance(s, basestring):
+ elif isinstance(s, str):
seq = _seq_str_to_seq(s, parse_single_int=parse_single_int)
if isinstance(seq, slice):
raise ValueError("using : to define axes is deprecated, please use .. instead")
- ticks = [seq] if isinstance(seq, (basestring, int)) else seq
+ ticks = [seq] if isinstance(seq, (str, int)) else seq
elif hasattr(s, '__array__'):
ticks = s.__array__()
else:
@@ -529,30 +524,28 @@ def _to_key(v, stack_depth=1, parse_single_int=False):
0['a0']
>>> _to_key('0.i[a0]')
0.i['a0']
-
- # evaluated variables do not work on Python 2, probably because the stackdepth is different
- # >>> ext = [1, 2, 3]
- # >>> _to_key('{ext} >> ext')
- # LGroup([1, 2, 3]) >> 'ext'
- # >>> answer = 42
- # >>> _to_key('{answer}')
- # 42
- # >>> _to_key('{answer} >> answer')
- # LGroup(42) >> 'answer'
- # >>> _to_key('10:{answer} >> answer')
- # LGroup(slice(10, 42, None)) >> 'answer'
- # >>> _to_key('4,{answer},2 >> answer')
- # LGroup([4, 42, 2]) >> 'answer'
- # >>> list(_to_key('40..{answer}'))
- # [40, 41, 42]
- # >>> _to_key('4,40..{answer},2')
- # [4, 40, 41, 42, 2]
- # >>> _to_key('4,40..{answer},2 >> answer')
- # LGroup([4, 40, 41, 42, 2]) >> 'answer'
+ >>> ext = [1, 2, 3]
+ >>> _to_key('{ext} >> ext')
+ LGroup([1, 2, 3]) >> 'ext'
+ >>> answer = 42
+ >>> _to_key('{answer}')
+ 42
+ >>> _to_key('{answer} >> answer')
+ LGroup(42) >> 'answer'
+ >>> _to_key('10:{answer} >> answer')
+ LGroup(slice(10, 42, None)) >> 'answer'
+ >>> _to_key('4,{answer},2 >> answer')
+ LGroup([4, 42, 2]) >> 'answer'
+ >>> list(_to_key('40..{answer}'))
+ [40, 41, 42]
+ >>> _to_key('4,40..{answer},2')
+ [4, 40, 41, 42, 2]
+ >>> _to_key('4,40..{answer},2 >> answer')
+ LGroup([4, 40, 41, 42, 2]) >> 'answer'
"""
if isinstance(v, tuple):
return list(v)
- elif isinstance(v, basestring):
+ elif isinstance(v, str):
# axis name
m = _axis_name_pattern.match(v)
_, axis, positional, key = m.groups()
@@ -608,12 +601,11 @@ def _to_keys(value, stack_depth=1):
>>> _to_keys('P01;P02,P03;:')
('P01', ['P02', 'P03'], slice(None, None, None))
- # evaluated variables do not work on Python 2, probably because the stack depth is different
- # >>> ext = 'P03'
- # >>> to_keys('P01,P02,{ext}')
- # ['P01', 'P02', 'P03']
- # >>> to_keys('P01;P02;{ext}')
- # ('P01', 'P02', 'P03')
+ >>> ext = 'P03'
+ >>> _to_keys('P01,P02,{ext}')
+ ['P01', 'P02', 'P03']
+ >>> _to_keys('P01;P02;{ext}')
+ ('P01', 'P02', 'P03')
>>> _to_keys('age[10:19] >> teens ; year.i[-1]')
(age[10:19] >> 'teens', year.i[-1])
@@ -633,7 +625,7 @@ def _to_keys(value, stack_depth=1):
>>> _to_keys((('P01',), ('P02',), ':'))
(['P01'], ['P02'], slice(None, None, None))
"""
- if isinstance(value, basestring) and ';' in value:
+ if isinstance(value, str) and ';' in value:
value = tuple(value.split(';'))
if isinstance(value, tuple):
@@ -650,7 +642,7 @@ def _to_keys(value, stack_depth=1):
def _translate_sheet_name(sheet_name):
if isinstance(sheet_name, Group):
sheet_name = str(_to_tick(sheet_name))
- if isinstance(sheet_name, basestring):
+ if isinstance(sheet_name, str):
sheet_name = _sheet_name_pattern.sub('_', sheet_name)
if len(sheet_name) > 31:
raise ValueError("Sheet names cannot exceed 31 characters")
@@ -740,7 +732,7 @@ def __init__(self, key, name=None, axis=None):
# we do NOT assign a name automatically when missing because that makes it impossible to know whether a name
# was explicitly given or not
self.name = _to_tick(name) if name is not None else name
- assert axis is None or isinstance(axis, (basestring, int, ABCAxis)), \
+ assert axis is None or isinstance(axis, (str, int, ABCAxis)), \
"invalid axis '%s' (%s)" % (axis, type(axis).__name__)
# we could check the key is valid but this can be slow and could be useless
@@ -836,7 +828,7 @@ def retarget_to(self, target_axis):
"""
if self.axis is target_axis:
return self
- elif isinstance(self.axis, basestring) or isinstance(self.axis, ABCAxisReference):
+ elif isinstance(self.axis, str) or isinstance(self.axis, ABCAxisReference):
axis_name = self.axis.name if isinstance(self.axis, ABCAxisReference) else self.axis
if axis_name != target_axis.name:
raise ValueError('cannot retarget a Group defined without a real axis object (e.g. using '
@@ -1020,26 +1012,9 @@ def _binop(opname):
op_fullname = '__%s__' % opname
# TODO: implement this in a delayed fashion for axes references
- if PY2:
- # workaround the fact slice objects do not have any __binop__ methods defined on Python2 (even though
- # the actual operations work on them).
- def opmethod(self, other):
- self_value = self.eval()
- other_value = other.eval() if isinstance(other, Group) else other
- # this can only happen when self.axis is not an Axis instance
- if isinstance(self_value, slice):
- if not isinstance(other_value, slice):
- # FIXME: we should raise a TypeError instead for all ops except == and !=
- # FIXME: we should return True for !=
- return False
- # FIXME: we should raise a TypeError instead of doing this for all ops except comparison ops
- self_value = (self_value.start, self_value.stop, self_value.step)
- other_value = (other_value.start, other_value.stop, other_value.step)
- return getattr(self_value, op_fullname)(other_value)
- else:
- def opmethod(self, other):
- other_value = other.eval() if isinstance(other, Group) else other
- return getattr(self.eval(), op_fullname)(other_value)
+ def opmethod(self, other):
+ other_value = other.eval() if isinstance(other, Group) else other
+ return getattr(self.eval(), op_fullname)(other_value)
opmethod.__name__ = op_fullname
return opmethod
diff --git a/larray/core/metadata.py b/larray/core/metadata.py
index 1fa6687d5..9f1531bb1 100644
--- a/larray/core/metadata.py
+++ b/larray/core/metadata.py
@@ -1,111 +1,24 @@
-# -*- coding: utf8 -*-
-from __future__ import absolute_import, division, print_function
-
from collections import OrderedDict
-from larray.util.compat import PY2, basestring
-
-
-if PY2:
- class AttributeDict(object):
- def __init__(self, *args, **kwargs):
- object.__setattr__(self, '__odict', OrderedDict(*args, **kwargs))
-
- def __getattr__(self, key):
- od = object.__getattribute__(self, '__odict')
- if hasattr(od, key):
- return getattr(od, key)
- else:
- try:
- return od[key]
- except KeyError:
- raise AttributeError(key)
-
- def __setattr__(self, key, value):
- od = object.__getattribute__(self, '__odict')
- od[key] = value
-
- def __delattr__(self, key):
- od = object.__getattribute__(self, '__odict')
- del od[key]
-
- def __reduce__(self):
- 'Return state information for pickling'
- od = object.__getattribute__(self, '__odict')
- res = list(od.__reduce__())
- res[0] = self.__class__
- return tuple(res)
-
- def __dir__(self):
- od = object.__getattribute__(self, '__odict')
- return list(set(dir(self.__class__)) | set(self.__dict__.keys()) | set(od.keys()))
-
- def copy(self):
- od = object.__getattribute__(self, '__odict')
- return self.__class__(od)
-
- def method_factory(name):
- fullname = '__%s__' % name
- odict_method = getattr(OrderedDict, fullname)
-
- def method(self, *args, **kwargs):
- od = object.__getattribute__(self, '__odict')
- return odict_method(od, *args, **kwargs)
- return method
-
- __getitem__ = method_factory('getitem')
- __setitem__ = method_factory('setitem')
- __delitem__ = method_factory('delitem')
- __contains__ = method_factory('contains')
-
- __iter__ = method_factory('iter')
- __len__ = method_factory('len')
-
- __reversed__ = method_factory('reversed')
-
- __sizeof__ = method_factory('sizeof')
-
- def _binop(name):
- fullname = '__%s__' % name
- odict_method = getattr(OrderedDict, fullname)
-
- def opmethod(self, other):
- self_od = object.__getattribute__(self, '__odict')
- if not isinstance(other, AttributeDict):
- return False
- other_od = object.__getattribute__(other, '__odict')
- return odict_method(self_od, other_od)
- opmethod.__name__ = fullname
- return opmethod
-
- __eq__ = _binop('eq')
- __ne__ = _binop('ne')
- __ge__ = _binop('ge')
- __gt__ = _binop('gt')
- __le__ = _binop('le')
- __lt__ = _binop('lt')
-
- def __repr__(self):
- return '\n'.join(['{}: {}'.format(k, v) for k, v in self.items()])
-
-else:
- class AttributeDict(OrderedDict):
- def __getattr__(self, key):
- try:
- return self[key]
- except KeyError:
- raise AttributeError(key)
-
- def __setattr__(self, key, value):
- self[key] = value
-
- def __delattr__(self, key):
- del self[key]
-
- def __dir__(self):
- return list(set(super(AttributeDict, self).__dir__()) | set(self.keys()))
-
- def __repr__(self):
- return '\n'.join(['{}: {}'.format(k, v) for k, v in self.items()])
+
+
+class AttributeDict(OrderedDict):
+ def __getattr__(self, key):
+ try:
+ return self[key]
+ except KeyError:
+ raise AttributeError(key)
+
+ def __setattr__(self, key, value):
+ self[key] = value
+
+ def __delattr__(self, key):
+ del self[key]
+
+ def __dir__(self):
+ return list(set(super(AttributeDict, self).__dir__()) | set(self.keys()))
+
+ def __repr__(self):
+ return '\n'.join(['{}: {}'.format(k, v) for k, v in self.items()])
class Metadata(AttributeDict):
@@ -120,10 +33,7 @@ class Metadata(AttributeDict):
Add metadata at array initialization
- >>> # Python 2 or <= 3.5
- >>> arr = ndtest((3, 3), meta=[('title', 'the title'), ('author', 'John Smith')])
- >>> # Python 3.6+
- >>> arr = ndtest((3, 3), meta=Metadata(title='the title', author='John Smith')) # doctest: +SKIP
+ >>> arr = ndtest((3, 3), meta=Metadata(title='the title', author='John Smith'))
Add metadata after array initialization
@@ -157,7 +67,7 @@ def from_array(cls, array):
def _convert_value(value):
value = to_numeric([value], errors='ignore')[0]
- if isinstance(value, basestring):
+ if isinstance(value, str):
value = to_datetime(value, errors='ignore', infer_datetime_format=True)
return value
diff --git a/larray/core/session.py b/larray/core/session.py
index 9c40f2028..1b395b1f4 100644
--- a/larray/core/session.py
+++ b/larray/core/session.py
@@ -1,12 +1,10 @@
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import, division, print_function
-
import os
import sys
import re
import fnmatch
import warnings
from collections import OrderedDict
+from collections.abc import Iterable
import numpy as np
@@ -16,7 +14,6 @@
from larray.core.constants import nan
from larray.core.array import Array, get_axes, ndtest, zeros, zeros_like, sequence, asarray
from larray.util.misc import float_error_handler_factory, is_interactive_interpreter, renamed_to, inverseop
-from larray.util.compat import basestring, Iterable
from larray.inout.session import ext_default_engine, get_file_handler
@@ -60,11 +57,11 @@ class Session(object):
>>> ses = Session([('i', i), ('s', s), ('a', a), ('b', b), ('a01', a01),
... ('arr1', arr1), ('arr2', arr2)])
- create a Session using keyword arguments (but you lose order on Python < 3.6)
+ create a Session using keyword arguments
>>> ses = Session(i=i, s=s, a=a, b=b, a01=a01, arr1=arr1, arr2=arr2)
- create a Session by passing a dictionary (but you lose order on Python < 3.6)
+ create a Session by passing a dictionary
>>> ses = Session({'i': i, 's': s, 'a': a, 'b': b, 'a01': a01, 'arr1': arr1, 'arr2': arr2})
@@ -74,13 +71,7 @@ class Session(object):
create a session with metadata
- >>> # Python <= 3.5
- >>> ses = Session([('arr1', arr1), ('arr2', arr2)], meta=[('title', 'my title'), ('author', 'John Smith')])
- >>> ses.meta
- title: my title
- author: John Smith
- >>> # Python 3.6+
- >>> ses = Session(arr1=arr1, arr2=arr2, meta=Metadata(title='my title', author='John Smith')) # doctest: +SKIP
+ >>> ses = Session(arr1=arr1, arr2=arr2, meta=Metadata(title='my title', author='John Smith'))
>>> ses.meta
title: my title
author: John Smith
@@ -104,6 +95,24 @@ def __init__(self, *args, **kwargs):
else:
self.add(*args, **kwargs)
+ @property
+ def meta(self):
+ r"""Returns metadata of the session.
+
+ Returns
+ -------
+ Metadata:
+ Metadata of the session.
+ """
+ return self._meta
+
+ @meta.setter
+ def meta(self, meta):
+ if not isinstance(meta, (list, dict, OrderedDict, Metadata)):
+ raise TypeError("Expected list of pairs or dict or OrderedDict or Metadata object "
+ "instead of {}".format(type(meta).__name__))
+ object.__setattr__(self, '_meta', meta if isinstance(meta, Metadata) else Metadata(meta))
+
# XXX: behave like a dict and return keys instead?
def __iter__(self):
return iter(self.values())
@@ -312,22 +321,17 @@ def __setitem__(self, key, value):
def __delitem__(self, key):
del self._objects[key]
- # TODO: add a a meta property when Python 2.7 will be dropped
def __getattr__(self, key):
- if key == 'meta':
- return self._meta
- elif key in self._objects:
+ if key in self._objects:
return self._objects[key]
else:
raise AttributeError("'{}' object has no attribute '{}'".format(self.__class__.__name__, key))
- # TODO: implement meta.setter when Python 2.7 will be dropped
def __setattr__(self, key, value):
+ # the condition below is needed because, unlike __getattr__, __setattr__ is called before any property
+ # see https://stackoverflow.com/a/15751159
if key == 'meta':
- if not isinstance(value, (list, dict, OrderedDict, Metadata)):
- raise TypeError("Expected list of pairs or dict or OrderedDict or Metadata object "
- "instead of {}".format(type(value).__name__))
- object.__setattr__(self, '_meta', value if isinstance(value, Metadata) else Metadata(value))
+ super().__setattr__(key, value)
else:
self._objects[key] = value
@@ -1451,10 +1455,10 @@ def display(k, v, is_metadata=False):
tmpl = template.get(t, "{key}: {value}")
else:
tmpl = template[Metadata]
- if not (isinstance(tmpl, basestring) or callable(tmpl)):
+ if not (isinstance(tmpl, str) or callable(tmpl)):
raise TypeError("Expected a string template or a function for type {}. "
"Got {}".format(type(v), type(tmpl)))
- if isinstance(tmpl, basestring):
+ if isinstance(tmpl, str):
if isinstance(v, Axis):
return tmpl.format(key=k, name=v.name, labels=v.labels_summary(), length=len(v))
elif isinstance(v, Group):
diff --git a/larray/example.py b/larray/example.py
index ca5c0ef8b..d7dc1af73 100644
--- a/larray/example.py
+++ b/larray/example.py
@@ -43,7 +43,7 @@ def get_example_filepath(fname):
# Note that we skip doctests because they require pytables, which is only an optional dependency and its hard
-# to skip doctests selectively. The tests output is also different between Python 2 and Python 3.
+# to skip doctests selectively.
# CHECK: We might want to use .csv files for the example data, so that it can be loaded with any optional dependency.
def load_example_data(name):
r"""Load arrays used in the tutorial so that all examples in it can be reproduced.
diff --git a/larray/inout/common.py b/larray/inout/common.py
index e05fa5c17..826009537 100644
--- a/larray/inout/common.py
+++ b/larray/inout/common.py
@@ -1,10 +1,7 @@
-from __future__ import absolute_import, print_function
-
import os
from datetime import date, time, datetime
from collections import OrderedDict
-from larray.util.compat import bytes, unicode
from larray.core.axis import Axis
from larray.core.group import Group
from larray.core.array import Array
@@ -15,8 +12,7 @@
# only for HDF5 and pickle formats
# support list, tuple and dict?
-# replace unicode by str when Python 2.7 will no longer be supported
-_supported_scalars_types = (int, float, bool, bytes, unicode, date, time, datetime)
+_supported_scalars_types = (int, float, bool, bytes, str, date, time, datetime)
_supported_types = _supported_larray_types + _supported_scalars_types
_supported_typenames = {cls.__name__ for cls in _supported_types}
diff --git a/larray/inout/csv.py b/larray/inout/csv.py
index 767f22fe3..c2190024f 100644
--- a/larray/inout/csv.py
+++ b/larray/inout/csv.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
import os
import csv
import warnings
@@ -13,7 +11,6 @@
from larray.core.constants import nan
from larray.core.metadata import Metadata
from larray.util.misc import skip_comment_cells, strip_rows, deprecate_kwarg
-from larray.util.compat import csv_open
from larray.inout.session import register_file_handler
from larray.inout.common import _get_index_col, FileHandler
from larray.inout.pandas import df_asarray
@@ -193,7 +190,7 @@ def read_csv(filepath_or_buffer, nb_axes=None, index_col=None, sep=',', headerse
# read axes names. This needs to be done separately instead of reading the whole file with Pandas then
# manipulating the dataframe because the header line must be ignored for the column types to be inferred
# correctly. Note that to read one line, this is faster than using Pandas reader.
- with csv_open(filepath_or_buffer) as f:
+ with open(filepath_or_buffer, mode='r', newline='', encoding='utf8') as f:
reader = csv.reader(f, delimiter=sep)
line_stream = skip_comment_cells(strip_rows(reader))
axes_names = next(line_stream)
diff --git a/larray/inout/excel.py b/larray/inout/excel.py
index 54b30c6a6..4d0f150c6 100644
--- a/larray/inout/excel.py
+++ b/larray/inout/excel.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
import os
import warnings
from collections import OrderedDict
diff --git a/larray/inout/hdf.py b/larray/inout/hdf.py
index b2c64315a..a3a3cadb3 100644
--- a/larray/inout/hdf.py
+++ b/larray/inout/hdf.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
import warnings
import numpy as np
diff --git a/larray/inout/misc.py b/larray/inout/misc.py
index e84d0e320..64b7be556 100644
--- a/larray/inout/misc.py
+++ b/larray/inout/misc.py
@@ -1,10 +1,9 @@
-from __future__ import absolute_import, print_function
+from io import StringIO
from pandas import DataFrame
from larray.core.constants import nan
from larray.util.misc import deprecate_kwarg
-from larray.util.compat import StringIO
from larray.inout.common import _get_index_col
from larray.inout.pandas import df_asarray, set_dataframe_index_by_position
from larray.inout.csv import read_csv
diff --git a/larray/inout/pandas.py b/larray/inout/pandas.py
index f8680a179..327fb8606 100644
--- a/larray/inout/pandas.py
+++ b/larray/inout/pandas.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
from itertools import product
import numpy as np
@@ -9,7 +7,14 @@
from larray.core.axis import Axis, AxisCollection
from larray.core.constants import nan
from larray.util.misc import unique
-from larray.util.compat import basestring, decode, bytes
+
+
+def decode(s, encoding='utf-8', errors='strict'):
+ if isinstance(s, bytes):
+ return s.decode(encoding, errors)
+ else:
+ assert s is None or isinstance(s, str), "unexpected " + str(type(s))
+ return s
def parse(s):
@@ -17,7 +22,7 @@ def parse(s):
Used to parse the "folded" axis ticks (usually periods).
"""
# parameters can be strings or numbers
- if isinstance(s, basestring):
+ if isinstance(s, str):
s = s.strip()
low = s.lower()
if low == 'true':
@@ -223,7 +228,7 @@ def from_frame(df, sort_rows=False, sort_columns=False, parse_header=False, unfo
# handle 2 or more dimensions with the last axis name given using \
if unfold_last_axis_name:
- if isinstance(axes_names[-1], basestring) and '\\' in axes_names[-1]:
+ if isinstance(axes_names[-1], str) and '\\' in axes_names[-1]:
last_axes = [name.strip() for name in axes_names[-1].split('\\')]
axes_names = axes_names[:-1] + last_axes
else:
@@ -317,7 +322,7 @@ def df_asarray(df, sort_rows=False, sort_columns=False, raw=False, parse_header=
if wide:
try:
# take the first column which contains '\'
- pos_last = next(i for i, v in enumerate(columns) if isinstance(v, basestring) and '\\' in v)
+ pos_last = next(i for i, v in enumerate(columns) if isinstance(v, str) and '\\' in v)
except StopIteration:
# we assume first column will not contain data
pos_last = 0
@@ -333,7 +338,7 @@ def df_asarray(df, sort_rows=False, sort_columns=False, raw=False, parse_header=
# handle 1D arrays
if len(df) == 1 and (pd.isnull(df.index.values[0]) or
- (isinstance(df.index.values[0], basestring) and df.index.values[0].strip() == '')):
+ (isinstance(df.index.values[0], str) and df.index.values[0].strip() == '')):
if parse_header:
df.columns = pd.Index([parse(cell) for cell in df.columns.values], name=df.columns.name)
series = df.iloc[0]
@@ -349,7 +354,7 @@ def parse_axis_name(name):
name = None
return name
axes_names = [parse_axis_name(name) for name in df.index.names]
- unfold_last_axis_name = isinstance(axes_names[-1], basestring) and '\\' in axes_names[-1]
+ unfold_last_axis_name = isinstance(axes_names[-1], str) and '\\' in axes_names[-1]
res = from_frame(df, sort_rows=sort_rows, sort_columns=sort_columns, parse_header=parse_header,
unfold_last_axis_name=unfold_last_axis_name, cartesian_prod=cartesian_prod, **kwargs)
@@ -358,6 +363,6 @@ def parse_axis_name(name):
# make them roundtrip correctly, based on the assumption that in an in-memory LArray an anonymouse axis is more
# likely and useful than an Axis with an empty name.
# TODO : find a more robust and elegant solution
- res = res.rename({axis: None for axis in res.axes if isinstance(axis.name, basestring) and
+ res = res.rename({axis: None for axis in res.axes if isinstance(axis.name, str) and
(axis.name == '' or 'Unnamed:' in axis.name)})
return res
diff --git a/larray/inout/pickle.py b/larray/inout/pickle.py
index 3f39c72a3..6bfa96d5e 100644
--- a/larray/inout/pickle.py
+++ b/larray/inout/pickle.py
@@ -1,5 +1,4 @@
-from __future__ import absolute_import, division, print_function
-
+import pickle
import os.path
from collections import OrderedDict
@@ -7,7 +6,6 @@
from larray.core.group import Group
from larray.core.array import Array
from larray.core.metadata import Metadata
-from larray.util.compat import pickle
from larray.inout.session import register_file_handler
from larray.inout.common import FileHandler, _supported_types, _supported_typenames, _supported_scalars_types
diff --git a/larray/inout/sas.py b/larray/inout/sas.py
index aed18aff9..f9fbf1263 100644
--- a/larray/inout/sas.py
+++ b/larray/inout/sas.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
import warnings
import numpy as np
diff --git a/larray/inout/session.py b/larray/inout/session.py
index c10f42b70..b587e1bf1 100644
--- a/larray/inout/session.py
+++ b/larray/inout/session.py
@@ -1,7 +1,3 @@
-from __future__ import absolute_import, division, print_function
-
-from larray.util.compat import basestring
-
handler_classes = {}
ext_default_engine = {}
@@ -22,7 +18,7 @@ def decorate_class(cls):
handler_classes[engine] = cls
if extensions is None:
exts = []
- elif isinstance(extensions, basestring):
+ elif isinstance(extensions, str):
exts = [extensions]
else:
exts = extensions
diff --git a/larray/inout/stata.py b/larray/inout/stata.py
index 5741e4525..79473e05f 100644
--- a/larray/inout/stata.py
+++ b/larray/inout/stata.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
import pandas as pd
from larray.inout.pandas import from_frame
diff --git a/larray/inout/xw_excel.py b/larray/inout/xw_excel.py
index d7bd61fbc..345b8a8c4 100644
--- a/larray/inout/xw_excel.py
+++ b/larray/inout/xw_excel.py
@@ -1,6 +1,3 @@
-# -*- coding: utf8 -*-
-from __future__ import absolute_import, print_function
-
import os
import atexit
@@ -17,7 +14,6 @@
from larray.inout.pandas import df_asarray
from larray.inout.misc import from_lists
from larray.util.misc import deprecate_kwarg
-from larray.util.compat import PY2
string_types = (str,)
@@ -649,8 +645,7 @@ def open_excel(filepath=None, overwrite_file=False, visible=None, silent=None, a
# We define Workbook and open_excel documentation here since Readthedocs runs on Linux
-if not PY2:
- Workbook.__doc__ = r"""
+Workbook.__doc__ = r"""
Excel Workbook.
See Also
@@ -658,7 +653,7 @@ def open_excel(filepath=None, overwrite_file=False, visible=None, silent=None, a
open_excel
"""
- Workbook.sheet_names.__doc__ = r"""
+Workbook.sheet_names.__doc__ = r"""
Returns the names of the Excel sheets.
Examples
@@ -674,7 +669,7 @@ def open_excel(filepath=None, overwrite_file=False, visible=None, silent=None, a
['arr', 'arr2', 'arr3']
"""
- Workbook.save.__doc__ = r"""
+Workbook.save.__doc__ = r"""
Saves the Workbook.
If a path is being provided, this works like SaveAs() in Excel.
@@ -697,7 +692,7 @@ def open_excel(filepath=None, overwrite_file=False, visible=None, silent=None, a
... wb.save()
"""
- Workbook.close.__doc__ = r"""
+Workbook.close.__doc__ = r"""
Close the workbook in Excel.
Need to be called if the workbook has been opened without the `with` statement.
@@ -713,7 +708,7 @@ def open_excel(filepath=None, overwrite_file=False, visible=None, silent=None, a
>>> wb.close() # doctest: +SKIP
"""
- Workbook.app.__doc__ = r"""
+Workbook.app.__doc__ = r"""
Return the Excel instance this workbook is attached to.
"""
diff --git a/larray/inout/xw_reporting.py b/larray/inout/xw_reporting.py
index fb7ae231a..9f0929641 100644
--- a/larray/inout/xw_reporting.py
+++ b/larray/inout/xw_reporting.py
@@ -3,7 +3,6 @@
from collections import OrderedDict
from larray.util.misc import _positive_integer, _validate_dir
-from larray.util.compat import PY2
from larray.core.group import _translate_sheet_name
from larray.core.array import asarray, zip_array_items
from larray.example import load_example_data, EXAMPLE_EXCEL_TEMPLATES_DIR
@@ -766,6 +765,5 @@ def __init__(self):
raise Exception("ExcelReport class cannot be instantiated because xlwings is not installed")
-if not PY2:
- ExcelReport.__doc__ = AbstractExcelReport.__doc__
- ReportSheet.__doc__ = AbstractReportSheet.__doc__
+ExcelReport.__doc__ = AbstractExcelReport.__doc__
+ReportSheet.__doc__ = AbstractReportSheet.__doc__
diff --git a/larray/ipfp/__init__.py b/larray/ipfp/__init__.py
index 953093522..c2138a17e 100644
--- a/larray/ipfp/__init__.py
+++ b/larray/ipfp/__init__.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
import warnings
import larray.extra.ipfp as ipfp
diff --git a/larray/tests/common.py b/larray/tests/common.py
index 2b3655a4f..4109450d8 100644
--- a/larray/tests/common.py
+++ b/larray/tests/common.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, division, print_function
-
import os
import sys
@@ -150,6 +148,4 @@ def meta():
needs_xlrd = pytest.mark.skipif(xlrd is None, reason="xlrd is required for this test")
needs_xlsxwriter = pytest.mark.skipif(xlsxwriter is None, reason="xlsxwriter is required for this test")
-needs_python35 = pytest.mark.skipif(sys.version_info < (3, 5), reason="Python 3.5 is required for this test")
-needs_python36 = pytest.mark.skipif(sys.version_info < (3, 6), reason="Python 3.6 is required for this test")
needs_python37 = pytest.mark.skipif(sys.version_info < (3, 7), reason="Python 3.7 is required for this test")
diff --git a/larray/tests/test_array.py b/larray/tests/test_array.py
index 8b6f41fc9..bbe17eadd 100644
--- a/larray/tests/test_array.py
+++ b/larray/tests/test_array.py
@@ -1,6 +1,3 @@
-# -*- coding: utf8 -*-
-from __future__ import absolute_import, division, print_function
-
import os
import re
import sys
@@ -8,19 +5,20 @@
import pytest
import numpy as np
import pandas as pd
+
+from io import StringIO
from collections import OrderedDict
from larray.tests.common import (inputpath, tmp_path, meta,
assert_array_equal, assert_array_nan_equal, assert_larray_equiv, assert_larray_equal,
needs_xlwings, needs_pytables, needs_xlsxwriter, needs_xlrd,
- needs_python35, needs_python36, needs_python37)
+ needs_python37)
from larray import (Array, LArray, Axis, LGroup, union, zeros, zeros_like, ndtest, empty, ones, eye, diag, stack,
clip, exp, where, X, mean, isnan, round, read_hdf, read_csv, read_eurostat, read_excel,
from_lists, from_string, open_excel, from_frame, sequence, nan, IGroup)
from larray.inout.pandas import from_series
from larray.core.axis import _to_ticks, _to_key
from larray.util.misc import LHDFStore
-from larray.util.compat import StringIO
from larray.core.metadata import Metadata
@@ -2553,10 +2551,9 @@ def test_transpose():
res = arr.transpose('b')
assert res.axes == [b, a, c]
- # using Ellipsis instead of ... to avoid a syntax error on Python 2 (where ... is only available within [])
- res = arr.transpose(Ellipsis, 'a')
+ res = arr.transpose(..., 'a')
assert res.axes == [b, c, a]
- res = arr.transpose('c', Ellipsis, 'a')
+ res = arr.transpose('c', ..., 'a')
assert res.axes == [c, b, a]
@@ -4560,35 +4557,32 @@ def test_diag():
assert d.i[1] == 1.0
-@needs_python35
def test_matmul():
# 2D / anonymous axes
a1 = ndtest([Axis(3), Axis(3)])
a2 = eye(3, 3) * 2
- # Note that we cannot use @ because that is an invalid syntax in Python 2
-
# Array value
- assert_array_equal(a1.__matmul__(a2), ndtest([Axis(3), Axis(3)]) * 2)
+ assert_array_equal(a1 @ a2, ndtest([Axis(3), Axis(3)]) * 2)
# ndarray value
- assert_array_equal(a1.__matmul__(a2.data), ndtest([Axis(3), Axis(3)]) * 2)
+ assert_array_equal(a1 @ a2.data, ndtest([Axis(3), Axis(3)]) * 2)
# non anonymous axes (N <= 2)
arr1d = ndtest(3)
arr2d = ndtest((3, 3))
# 1D @ 1D
- res = arr1d.__matmul__(arr1d)
+ res = arr1d @ arr1d
assert isinstance(res, np.integer)
assert res == 5
# 1D @ 2D
- assert_array_equal(arr1d.__matmul__(arr2d),
+ assert_array_equal(arr1d @ arr2d,
Array([15, 18, 21], 'b=b0..b2'))
# 2D @ 1D
- assert_array_equal(arr2d.__matmul__(arr1d),
+ assert_array_equal(arr2d @ arr1d,
Array([5, 14, 23], 'a=a0..a2'))
# 2D(a,b) @ 2D(a,b) -> 2D(a,b)
@@ -4596,20 +4590,18 @@ def test_matmul():
['a0', 15, 18, 21],
['a1', 42, 54, 66],
['a2', 69, 90, 111]])
- assert_array_equal(arr2d.__matmul__(arr2d), res)
+ assert_array_equal(arr2d @ arr2d, res)
# 2D(a,b) @ 2D(b,a) -> 2D(a,a)
res = from_lists([['a\\a', 'a0', 'a1', 'a2'],
['a0', 5, 14, 23],
['a1', 14, 50, 86],
['a2', 23, 86, 149]])
- assert_array_equal(arr2d.__matmul__(arr2d.T), res)
+ assert_array_equal(arr2d @ arr2d.T, res)
# ndarray value
- assert_array_equal(arr1d.__matmul__(arr2d.data),
- Array([15, 18, 21]))
- assert_array_equal(arr2d.data.__matmul__(arr2d.T.data),
- res.data)
+ assert_array_equal(arr1d @ arr2d.data, Array([15, 18, 21]))
+ assert_array_equal(arr2d.data @ arr2d.T.data, res.data)
# different axes
a1 = ndtest('a=a0..a1;b=b0..b2')
@@ -4617,7 +4609,7 @@ def test_matmul():
res = from_lists([[r'a\c', 'c0', 'c1', 'c2', 'c3'],
['a0', 20, 23, 26, 29],
['a1', 56, 68, 80, 92]])
- assert_array_equal(a1.__matmul__(a2), res)
+ assert_array_equal(a1 @ a2, res)
# non anonymous axes (N >= 2)
arr2d = ndtest((2, 2))
@@ -4646,7 +4638,7 @@ def test_matmul():
['a1', 'b1', 'e0', 'c1', 30, 59],
['a1', 'b1', 'e1', 'c0', 126, 151],
['a1', 'b1', 'e1', 'c1', 146, 175]])
- assert_array_equal(arr4d.__matmul__(arr3d), res)
+ assert_array_equal(arr4d @ arr3d, res)
# 3D(e, d, f) @ 4D(a, b, c, d) -> 5D(e, a, b, d, d)
res = from_lists([['e', 'a', 'b', 'd\\d', 'd0', 'd1'],
@@ -4666,7 +4658,7 @@ def test_matmul():
['e1', 'a1', 'b0', 'd1', 118, 131],
['e1', 'a1', 'b1', 'd0', 118, 127],
['e1', 'a1', 'b1', 'd1', 170, 183]])
- assert_array_equal(arr3d.__matmul__(arr4d), res)
+ assert_array_equal(arr3d @ arr4d, res)
# 4D(a, b, c, d) @ 3D(b, d, f) -> 4D(a, b, c, f)
arr3d = arr3d.set_axes([b, d, f])
@@ -4679,7 +4671,7 @@ def test_matmul():
['a1', 'b0', 'c1', 22, 43],
['a1', 'b1', 'c0', 126, 151],
['a1', 'b1', 'c1', 146, 175]])
- assert_array_equal(arr4d.__matmul__(arr3d), res)
+ assert_array_equal(arr4d @ arr3d, res)
# 3D(b, d, f) @ 4D(a, b, c, d) -> 4D(b, a, d, d)
res = from_lists([['b', 'a', 'd\\d', 'd0', 'd1'],
@@ -4691,7 +4683,7 @@ def test_matmul():
['b1', 'a0', 'd1', 66, 79],
['b1', 'a1', 'd0', 118, 127],
['b1', 'a1', 'd1', 170, 183]])
- assert_array_equal(arr3d.__matmul__(arr4d), res)
+ assert_array_equal(arr3d @ arr4d, res)
# 4D(a, b, c, d) @ 2D(d, f) -> 5D(a, b, c, f)
arr2d = arr2d.set_axes([d, f])
@@ -4704,7 +4696,7 @@ def test_matmul():
['a1', 'b0', 'c1', 22, 43],
['a1', 'b1', 'c0', 26, 51],
['a1', 'b1', 'c1', 30, 59]])
- assert_array_equal(arr4d.__matmul__(arr2d), res)
+ assert_array_equal(arr4d @ arr2d, res)
# 2D(d, f) @ 4D(a, b, c, d) -> 5D(a, b, d, d)
res = from_lists([['a', 'b', 'd\\d', 'd0', 'd1'],
@@ -4716,10 +4708,9 @@ def test_matmul():
['a1', 'b0', 'd1', 46, 51],
['a1', 'b1', 'd0', 14, 15],
['a1', 'b1', 'd1', 66, 71]])
- assert_array_equal(arr2d.__matmul__(arr4d), res)
+ assert_array_equal(arr2d @ arr4d, res)
-@needs_python35
def test_rmatmul():
a1 = eye(3) * 2
a2 = ndtest([Axis(3), Axis(3)])
@@ -5077,7 +5068,6 @@ def test_stack():
assert_array_equal(res, expected)
-@needs_python36
def test_stack_kwargs_no_axis_labels():
# these tests rely on kwargs ordering, hence python 3.6
diff --git a/larray/tests/test_axis.py b/larray/tests/test_axis.py
index fd9bb4bbd..059b60bf0 100644
--- a/larray/tests/test_axis.py
+++ b/larray/tests/test_axis.py
@@ -1,6 +1,3 @@
-# -*- coding: utf8 -*-
-from __future__ import absolute_import, division, print_function
-
import pytest
import os.path
import numpy as np
diff --git a/larray/tests/test_axiscollection.py b/larray/tests/test_axiscollection.py
index 4846114e8..ca89f1784 100644
--- a/larray/tests/test_axiscollection.py
+++ b/larray/tests/test_axiscollection.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, division, print_function
-
import numpy as np
import pytest
diff --git a/larray/tests/test_example.py b/larray/tests/test_example.py
index c7107492e..037a0808c 100644
--- a/larray/tests/test_example.py
+++ b/larray/tests/test_example.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, division, print_function
-
import pytest
from larray.tests.common import needs_pytables
diff --git a/larray/tests/test_excel.py b/larray/tests/test_excel.py
index a3ef75d3c..ca2b42e93 100644
--- a/larray/tests/test_excel.py
+++ b/larray/tests/test_excel.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, division, print_function
-
import re
import os
diff --git a/larray/tests/test_group.py b/larray/tests/test_group.py
index a75f99f9b..a6c974cf0 100644
--- a/larray/tests/test_group.py
+++ b/larray/tests/test_group.py
@@ -1,6 +1,3 @@
-# -*- coding: utf8 -*-
-from __future__ import absolute_import, division, print_function
-
import pytest
import os.path
import numpy as np
diff --git a/larray/tests/test_ipfp.py b/larray/tests/test_ipfp.py
index 2eeb75990..d40a9e461 100644
--- a/larray/tests/test_ipfp.py
+++ b/larray/tests/test_ipfp.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, division, print_function
-
import pytest
from larray.tests.common import assert_array_equal
from larray import Axis, Array, ndtest, ipfp, X
diff --git a/larray/tests/test_options.py b/larray/tests/test_options.py
index c37d21633..b3ded4eaf 100644
--- a/larray/tests/test_options.py
+++ b/larray/tests/test_options.py
@@ -1,4 +1,3 @@
-from __future__ import absolute_import, division, print_function
import pytest
import larray
diff --git a/larray/tests/test_session.py b/larray/tests/test_session.py
index 9d9f22410..39590b127 100644
--- a/larray/tests/test_session.py
+++ b/larray/tests/test_session.py
@@ -1,7 +1,6 @@
-from __future__ import absolute_import, division, print_function
-
import os
import shutil
+import pickle
from datetime import date, time, datetime
import numpy as np
@@ -13,7 +12,6 @@
from larray.inout.common import _supported_scalars_types
from larray import (Session, Axis, Array, Group, isnan, zeros_like, ndtest, ones_like, ones, full,
local_arrays, global_arrays, arrays)
-from larray.util.compat import pickle, PY2
def equal(o1, o2):
@@ -192,7 +190,7 @@ def _test_io(fpath, session, meta, engine):
# use Session.names instead of Session.keys because CSV, Excel and HDF do *not* keep ordering
assert s.names == session.names
assert s.equals(session)
- if not PY2 and not is_excel_or_csv:
+ if not is_excel_or_csv:
for key in s.filter(kind=Axis).keys():
assert s[key].dtype == session[key].dtype
if engine != 'pandas_excel':
diff --git a/larray/util/compat.py b/larray/util/compat.py
deleted file mode 100644
index 1244dfba6..000000000
--- a/larray/util/compat.py
+++ /dev/null
@@ -1,58 +0,0 @@
-import sys
-
-try:
- # the abstract base classes were moved to the abc sub-module in Python 3.3 but there is a backward compatibility
- # layer for Python up to 3.7
- from collections.abc import Iterable, Sequence
-except ImportError:
- # needed for Python < 3.3 (including 2.7)
- from collections import Iterable, Sequence
-
-try:
- import builtins
-except ImportError:
- import __builtin__ as builtins
-
-try:
- from itertools import izip
-except ImportError:
- izip = zip
-
-if sys.version_info[0] < 3:
- basestring = basestring
- bytes = str
- unicode = unicode
- long = long
- PY2 = True
-else:
- basestring = str
- bytes = bytes
- unicode = str
- long = int
- PY2 = False
-
-if PY2:
- from StringIO import StringIO
-else:
- from io import StringIO
-
-if PY2:
- import cPickle as pickle
-else:
- import pickle
-
-
-def csv_open(filename, mode='r'):
- assert 'b' not in mode and 't' not in mode
- if PY2:
- return open(filename, mode + 'b')
- else:
- return open(filename, mode, newline='', encoding='utf8')
-
-
-def decode(s, encoding='utf-8', errors='strict'):
- if isinstance(s, bytes):
- return s.decode(encoding, errors)
- else:
- assert s is None or isinstance(s, unicode), "unexpected " + str(type(s))
- return s
diff --git a/larray/util/misc.py b/larray/util/misc.py
index 95f9ae5f7..d1ca4ea0e 100644
--- a/larray/util/misc.py
+++ b/larray/util/misc.py
@@ -1,7 +1,6 @@
"""
Misc tools
"""
-from __future__ import absolute_import, division, print_function
import __main__
import math
@@ -18,8 +17,6 @@
import numpy as np
import pandas as pd
-from larray.util.compat import PY2
-
try:
np.set_printoptions(legacy='1.13')
except TypeError:
@@ -799,7 +796,7 @@ def __getitem__(self, key):
return SequenceZip([seq[key] for seq in self.sequences])
def __iter__(self):
- return iter(zip(*self.sequences)) if PY2 else zip(*self.sequences)
+ return zip(*self.sequences)
def __repr__(self):
return 'SequenceZip({})'.format(self.sequences)
diff --git a/larray/util/options.py b/larray/util/options.py
index ed50e5713..1af7ee696 100644
--- a/larray/util/options.py
+++ b/larray/util/options.py
@@ -1,4 +1,3 @@
-from __future__ import absolute_import, division, print_function
from larray.util.misc import _positive_integer
DISPLAY_PRECISION = 'display_precision'
diff --git a/larray/viewer/__init__.py b/larray/viewer/__init__.py
index 346e08e0e..7a3168366 100644
--- a/larray/viewer/__init__.py
+++ b/larray/viewer/__init__.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, division, print_function
-
def view(obj=None, title='', depth=0):
r"""
diff --git a/setup.py b/setup.py
index db97a1522..95bf89035 100644
--- a/setup.py
+++ b/setup.py
@@ -30,10 +30,7 @@ def readlocal(fname):
'Intended Audience :: Science/Research',
'Intended Audience :: Developers',
'Programming Language :: Python',
- 'Programming Language :: Python :: 2',
- 'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Scientific/Engineering',