From 8b8922dc250888cc53560f37840cc0054a029860 Mon Sep 17 00:00:00 2001
From: Jean-Baptiste Keck <Jean-Baptiste.Keck@imag.fr>
Date: Thu, 29 Oct 2020 23:30:26 +0100
Subject: [PATCH] metaclasses

---
 hysop/backend/device/autotunable_kernel.py    |   3 +-
 .../device/codegen/functions/complex.py       |   4 +-
 .../device/codegen/kernels/custom_symbolic.py |   4 +-
 hysop/backend/device/device_platform.py       |  10 +-
 hysop/backend/device/kernel_autotuner.py      |   3 +-
 .../backend/device/kernel_autotuner_config.py |   4 +-
 hysop/backend/device/kernel_config.py         |   8 +-
 hysop/backend/device/logical_device.py        |  42 +++--
 .../backend/device/opencl/opencl_allocator.py |  13 +-
 .../device/opencl/opencl_kernel_launcher.py   |   7 +-
 .../backend/device/opencl/opencl_operator.py  |   3 +-
 hysop/backend/hardware/hardware_backend.py    |   8 +-
 hysop/backend/hardware/hwinfo.py              |   7 +-
 .../backend/host/fortran/fortran_operator.py  |   9 +-
 .../backend/host/host_directional_operator.py |   8 +-
 hysop/backend/host/host_operator.py           |   3 +-
 hysop/core/arrays/array.py                    | 156 +++++++++---------
 hysop/core/arrays/array_backend.py            |   4 +-
 hysop/core/graph/allocator.py                 |   3 +-
 hysop/core/graph/computational_graph.py       |   4 +-
 hysop/core/graph/computational_node.py        |   4 +-
 hysop/core/graph/computational_operator.py    |   4 +-
 hysop/core/graph/continuous.py                |  25 ++-
 hysop/core/graph/node_generator.py            |   4 +-
 hysop/core/memory/allocator.py                |   4 +-
 hysop/core/memory/mempool.py                  |   4 +-
 hysop/domain/domain.py                        |   6 +-
 hysop/fields/discrete_field.py                |  20 +--
 hysop/fields/ghost_exchangers.py              |   3 +-
 hysop/mesh/mesh.py                            |  22 ++-
 hysop/numerics/fft/fft.py                     | 104 ++++++------
 hysop/operator/adapt_timestep.py              |   3 +-
 .../operator/base/custom_symbolic_operator.py |   3 +-
 hysop/operator/base/derivative.py             |   4 +-
 hysop/operator/base/enstrophy.py              |   4 +-
 hysop/operator/base/external_force.py         |   3 +-
 hysop/operator/base/integrate.py              |   4 +-
 hysop/operator/base/memory_reordering.py      |   4 +-
 hysop/operator/base/redistribute_operator.py  |  38 ++---
 hysop/operator/base/transpose_operator.py     |  46 +++---
 hysop/operator/directional/directional.py     |   7 +-
 hysop/operator/hdf_io.py                      |   4 +-
 hysop/parameters/parameter.py                 |   3 +-
 hysop/tools/enum.py                           |   3 +-
 hysop/tools/handle.py                         |  12 +-
 hysop/tools/interface.py                      |  64 ++++---
 hysop/tools/transposition_states.py           |   3 +-
 hysop/tools/variable.py                       |   5 +-
 hysop/topology/topology.py                    |  88 +++++-----
 hysop/topology/topology_descriptor.py         |  19 +--
 50 files changed, 362 insertions(+), 458 deletions(-)

diff --git a/hysop/backend/device/autotunable_kernel.py b/hysop/backend/device/autotunable_kernel.py
index 170f3348e..0bec40f40 100644
--- a/hysop/backend/device/autotunable_kernel.py
+++ b/hysop/backend/device/autotunable_kernel.py
@@ -8,8 +8,7 @@ from hysop.backend.device.kernel_autotuner_config import KernelAutotunerConfig
 from hysop.backend.device.codegen.structs.mesh_info import MeshInfoStruct
 from hysop.fields.cartesian_discrete_field import CartesianDiscreteScalarFieldView
 
-class AutotunableKernel(object):
-    __metaclass__ = ABCMeta
+class AutotunableKernel(object, metaclass=ABCMeta):
 
     def __init__(self, autotuner_config, build_opts,
                 dump_src=None, symbolic_mode=None, **kwds):
diff --git a/hysop/backend/device/codegen/functions/complex.py b/hysop/backend/device/codegen/functions/complex.py
index 5b29dd56f..3f2e68faf 100644
--- a/hysop/backend/device/codegen/functions/complex.py
+++ b/hysop/backend/device/codegen/functions/complex.py
@@ -8,9 +8,7 @@ from hysop.backend.device.codegen.base.utils            import WriteOnceDict, Ar
 from hysop.backend.device.codegen.base.statistics       import WorkStatistics
 from hysop.backend.device.opencl.opencl_types           import OpenClTypeGen, basetype
 
-class OpenClComplexOperator(OpenClFunctionCodeGenerator):
-
-    __metaclass__ = ABCMeta
+class OpenClComplexOperator(OpenClFunctionCodeGenerator, metaclass=ABCMeta):
 
     def __init__(self, typegen, ftype, vectorization, output=None, known_args=None):
 
diff --git a/hysop/backend/device/codegen/kernels/custom_symbolic.py b/hysop/backend/device/codegen/kernels/custom_symbolic.py
index 066f9382e..df977b761 100644
--- a/hysop/backend/device/codegen/kernels/custom_symbolic.py
+++ b/hysop/backend/device/codegen/kernels/custom_symbolic.py
@@ -384,9 +384,7 @@ class SymbolicCodegenContext(object):
         return args
 
 
-class CustomSymbolicKernelGenerator(KernelCodeGenerator):
-
-    __metaclass__ = ABCMeta
+class CustomSymbolicKernelGenerator(KernelCodeGenerator, metaclass=ABCMeta):
 
     @classmethod
     def create(cls, expr_info, **kwds):
diff --git a/hysop/backend/device/device_platform.py b/hysop/backend/device/device_platform.py
index 096eec384..63df8bdde 100644
--- a/hysop/backend/device/device_platform.py
+++ b/hysop/backend/device/device_platform.py
@@ -1,9 +1,7 @@
 
 from abc import ABCMeta, abstractmethod
 
-class Platform(object):
-
-    __metaclass__ = ABCMeta
+class Platform(object, metaclass=ABCMeta):
 
     def __init__(self, hardware_topo, platform_handle, platform_id, **kwds):
         super(Platform, self).__init__(**kwds)
@@ -12,11 +10,11 @@ class Platform(object):
         self._discover_devices(hardware_topo, platform_handle)
         # we do not keep a reference to platform_handle as we
         # need to pickle this object
-    
+
     @property
     def platform_id(self):
         return self._platform_id
-    
+
     @property
     def logical_devices(self):
         return self._logical_devices
@@ -24,7 +22,7 @@ class Platform(object):
     @property
     def physical_devices(self):
         return [dev.physical_device() for dev in self.logical_devices()]
-    
+
     @abstractmethod
     def _discover_devices(self, hardware_topo, platform_handle):
         pass
diff --git a/hysop/backend/device/kernel_autotuner.py b/hysop/backend/device/kernel_autotuner.py
index 7bb7792d9..f4a6e9096 100644
--- a/hysop/backend/device/kernel_autotuner.py
+++ b/hysop/backend/device/kernel_autotuner.py
@@ -18,8 +18,7 @@ from hysop.backend.device.codegen import CodeGeneratorWarning
 class KernelGenerationError(RuntimeError):
     pass
 
-class KernelAutotuner(object):
-    __metaclass__ = ABCMeta
+class KernelAutotuner(object, metaclass=ABCMeta):
 
     FULL_RESULTS_KEY = '__FULL_RESULTS__'
     DUMP_LAST_TUNED_KERNEL    = False
diff --git a/hysop/backend/device/kernel_autotuner_config.py b/hysop/backend/device/kernel_autotuner_config.py
index ee4483e49..b8ed3d302 100644
--- a/hysop/backend/device/kernel_autotuner_config.py
+++ b/hysop/backend/device/kernel_autotuner_config.py
@@ -4,9 +4,7 @@ from hysop.constants import AutotunerFlags, \
                             __DEBUG__, __VERBOSE__, __KERNEL_DEBUG__, \
                             DEFAULT_AUTOTUNER_FLAG, DEFAULT_AUTOTUNER_PRUNE_THRESHOLD
 
-class KernelAutotunerConfig(object):
-
-    __metaclass__ = ABCMeta
+class KernelAutotunerConfig(object, metaclass=ABCMeta):
 
     _default_initial_runs = {
         AutotunerFlags.ESTIMATE:   1,
diff --git a/hysop/backend/device/kernel_config.py b/hysop/backend/device/kernel_config.py
index 334ab9e34..6db662404 100644
--- a/hysop/backend/device/kernel_config.py
+++ b/hysop/backend/device/kernel_config.py
@@ -5,9 +5,7 @@ from hysop.constants import Precision
 from hysop.backend.device.kernel_autotuner_config import KernelAutotunerConfig
 from hysop.tools.types import check_instance, first_not_None
 
-class KernelConfig(object):
-
-    __metaclass__ = ABCMeta
+class KernelConfig(object, metaclass=ABCMeta):
 
     def __init__(self, autotuner_config=None,
                 user_build_options=None,
@@ -15,7 +13,7 @@ class KernelConfig(object):
                 precision=None,
                 float_dump_mode=None,
                 use_short_circuit_ops=None,
-                unroll_loops=None): 
+                unroll_loops=None):
 
         autotuner_config = first_not_None(autotuner_config, self.default_autotuner_config())
         user_build_options = first_not_None(user_build_options, [])
@@ -23,7 +21,7 @@ class KernelConfig(object):
         precision = first_not_None(precision, Precision.SAME)
         use_short_circuit_ops = first_not_None(use_short_circuit_ops, False)
         unroll_loops = first_not_None(unroll_loops, False)
-        
+
         if (float_dump_mode is None):
             if __KERNEL_DEBUG__:
                 float_dump_mode = 'dec'
diff --git a/hysop/backend/device/logical_device.py b/hysop/backend/device/logical_device.py
index d9e74c926..44682869e 100644
--- a/hysop/backend/device/logical_device.py
+++ b/hysop/backend/device/logical_device.py
@@ -10,11 +10,9 @@ class UnknownDeviceAttribute(object):
         return 'unknown'
 
 
-class LogicalDevice(object):
+class LogicalDevice(object, metaclass=ABCMeta):
 
-    __metaclass__ = ABCMeta
-    
-    def __init__(self, platform, platform_handle, device_id, device_handle, 
+    def __init__(self, platform, platform_handle, device_id, device_handle,
             hardware_topo, **kargs):
         super(LogicalDevice,self).__init__(**kargs)
         self._platform = platform
@@ -22,11 +20,11 @@ class LogicalDevice(object):
         physical_devices = self._match_physical_devices(hardware_topo=hardware_topo)
         physical_devices = to_tuple(physical_devices)
         self._physical_devices = physical_devices
-        
+
         vendor = hardware_topo.pciids.find_vendor(self.vendor_id())
-        self._vendor_handle = vendor 
+        self._vendor_handle = vendor
         self._device_handle = None
-        
+
         # identifying device without device_id is not easy for CPUs
         # so we do not look for a device handle
         if (physical_devices is not None):
@@ -50,15 +48,15 @@ class LogicalDevice(object):
             return ' [0x{:04x}]'.format(did)
         else:
             return ''
-    
+
     @property
     def device_id(self):
         return self._device_id
-        
+
     @property
     def platform(self):
         return self._platform
-    
+
     @property
     def physical_devices(self):
         return self._physical_devices
@@ -66,7 +64,7 @@ class LogicalDevice(object):
     @abstractmethod
     def _match_physical_devices(self, hardware_topo):
         pass
-    
+
     @abstractmethod
     def _determine_performance_and_affinity(self, hardware_topo):
         pass
@@ -153,14 +151,14 @@ class LogicalDevice(object):
     @abstractmethod
     def max_global_alloc_size(self):
         pass
-    
+
     @abstractmethod
     def local_mem_size(self):
         pass
     @abstractmethod
     def local_mem_type(self):
         pass
-    
+
 
 #DEVICE SPLITTING
     @abstractmethod
@@ -194,7 +192,7 @@ class LogicalDevice(object):
     @abstractmethod
     def fp64_config(self):
         pass
-    
+
 #IMAGES
     def has_image_support(self):
         pass
@@ -206,14 +204,14 @@ class LogicalDevice(object):
         pass
     def max_samplers(self):
         pass
-    
+
     def has_1d_image_support(self):
         pass
     def has_2d_image_support(self):
         pass
     def has_3d_image_support(self):
         pass
-    
+
     def has_1d_image_write_support(self):
         pass
     def has_2d_image_write_support(self):
@@ -225,7 +223,7 @@ class LogicalDevice(object):
         pass
     def has_2d_array_image_support(self):
         pass
-    
+
     def max_1d_image_size(self):
         pass
     def max_1d_image_array_size(self):
@@ -238,7 +236,7 @@ class LogicalDevice(object):
 
     def max_3d_image_size(self):
         pass
-   
+
 
     def has_2d_image_from_buffer_support(self):
         pass
@@ -254,7 +252,7 @@ class LogicalDevice(object):
     def image_max_array_size(self):
         pass
 
-    
+
 #ATOMICS
     @abstractmethod
     def has_global_int32_atomics(self):
@@ -268,7 +266,7 @@ class LogicalDevice(object):
     @abstractmethod
     def has_global_float64_atomics(self):
         pass
-    
+
     @abstractmethod
     def has_local_int32_atomics(self):
         pass
@@ -281,7 +279,7 @@ class LogicalDevice(object):
     @abstractmethod
     def has_local_float64_atomics(self):
         pass
-    
+
     @abstractmethod
     def has_mixed_int32_atomics(self):
         pass
@@ -294,7 +292,7 @@ class LogicalDevice(object):
     @abstractmethod
     def has_mixed_float64_atomics(self):
         pass
-    
+
     @abstractmethod
     def has_int32_hardware_atomic_counters(self):
         pass
diff --git a/hysop/backend/device/opencl/opencl_allocator.py b/hysop/backend/device/opencl/opencl_allocator.py
index 44d8f8b0c..e44b2a0f4 100644
--- a/hysop/backend/device/opencl/opencl_allocator.py
+++ b/hysop/backend/device/opencl/opencl_allocator.py
@@ -5,11 +5,10 @@ from hysop.backend.device.opencl import cl, cl_api
 from hysop.core.memory.allocator import AllocatorBase
 from hysop.backend.device.opencl.opencl_buffer import OpenClBuffer
 
-class OpenClAllocator(AllocatorBase):
+class OpenClAllocator(AllocatorBase, metaclass=ABCMeta):
     """
     Base class for OpenCl backend allocators.
     """
-    __metaclass__=ABCMeta
 
     def __init__(self, queue, mem_flags=cl.mem_flags.READ_WRITE, verbose=None):
         super(OpenClAllocator, self).__init__(verbose=verbose)
@@ -25,7 +24,7 @@ class OpenClAllocator(AllocatorBase):
     def max_alloc_size(self):
         """Max allocatable size in bytes."""
         return self._max_alloc_size
-    
+
     def get_queue(self):
         return self._queue
     def get_context(self):
@@ -47,10 +46,10 @@ class OpenClAllocator(AllocatorBase):
         """
         super(OpenClAllocator, self).allocate(nbytes=nbytes, **kwds)
         try:
-            return self._allocate_impl(nbytes=nbytes) 
+            return self._allocate_impl(nbytes=nbytes)
         except cl.Error as e:
             raise MemoryError(str(e))
-    
+
     def is_on_host(self):
         """
         Return true if buffers are allocated in host memory.
@@ -98,7 +97,7 @@ class OpenClImmediateAllocator(OpenClAllocator):
             raise MemoryError(str(e))
 
         return buf
-   
+
     def memory_pool(self, name, **kwds):
         """
         Construct a memory pool from this allocator.
@@ -107,4 +106,4 @@ class OpenClImmediateAllocator(OpenClAllocator):
         if isinstance(self, MemoryPool):
             msg='allocator is already a memory pool.'
             raise RuntimeError(msg)
-        return OpenClMemoryPool(allocator=self, name=name, verbose=None, **kwds) 
+        return OpenClMemoryPool(allocator=self, name=name, verbose=None, **kwds)
diff --git a/hysop/backend/device/opencl/opencl_kernel_launcher.py b/hysop/backend/device/opencl/opencl_kernel_launcher.py
index 0d203fc63..b96823f27 100644
--- a/hysop/backend/device/opencl/opencl_kernel_launcher.py
+++ b/hysop/backend/device/opencl/opencl_kernel_launcher.py
@@ -251,11 +251,10 @@ class OpenClKernelListLauncher(object):
     statistics = property(_get_statistics)
 
 
-class LauncherI(object):
+class LauncherI(object, metaclass=ABCMeta):
     """
     Interface for any object that has the ability to be a launcher.
     """
-    __metaclass__ = ABCMeta
 
     def __init__(self, name, **kwds):
         """
@@ -595,11 +594,9 @@ class OpenClParametrizedKernelLauncher(OpenClKernelLauncher):
     parameters_map = property(_get_parameters_map)
 
 
-class OpenClKernelParameterGenerator(object):
+class OpenClKernelParameterGenerator(object, metaclass=ABCMeta):
     """Abstract base for opencl kernel parameter yielders."""
 
-    __metaclass__ = ABCMeta
-
     def __iter__(self):
         return self.new_generator()
 
diff --git a/hysop/backend/device/opencl/opencl_operator.py b/hysop/backend/device/opencl/opencl_operator.py
index b88796c7b..535925701 100644
--- a/hysop/backend/device/opencl/opencl_operator.py
+++ b/hysop/backend/device/opencl/opencl_operator.py
@@ -21,11 +21,10 @@ from hysop.topology.topology import Topology, TopologyView
 from hysop.topology.topology_descriptor import TopologyDescriptor
 from hysop.fields.discrete_field import DiscreteScalarFieldView
 
-class OpenClOperator(ComputationalGraphOperator):
+class OpenClOperator(ComputationalGraphOperator, metaclass=ABCMeta):
     """
     Abstract class for discrete operators working on OpenCL backends.
     """
-    __metaclass__ = ABCMeta
 
     __default_method = {
             OpenClKernelConfig: OpenClKernelConfig()
diff --git a/hysop/backend/hardware/hardware_backend.py b/hysop/backend/hardware/hardware_backend.py
index 21fa5ab05..7c8031911 100644
--- a/hysop/backend/hardware/hardware_backend.py
+++ b/hysop/backend/hardware/hardware_backend.py
@@ -1,19 +1,17 @@
 
 from abc import ABCMeta, abstractmethod
 
-class HardwareBackend(object):
-
-    __metaclass__ = ABCMeta
+class HardwareBackend(object, metaclass=ABCMeta):
 
     def __init__(self, hardware_topo, **kargs):
         super(HardwareBackend,self).__init__(**kargs)
         self._platforms = {}
         self._discover_platforms(hardware_topo)
-    
+
     @property
     def platforms(self):
         return self._platforms
-    
+
     @abstractmethod
     def _discover_platforms(self, hardware_topo):
         pass
diff --git a/hysop/backend/hardware/hwinfo.py b/hysop/backend/hardware/hwinfo.py
index 227203cac..5510a00d8 100644
--- a/hysop/backend/hardware/hwinfo.py
+++ b/hysop/backend/hardware/hwinfo.py
@@ -18,14 +18,12 @@ from hysop.tools.cache import load_data_from_cache, update_cache, machine_id
 from hysop.backend.hardware.pci_ids import PCIIds
 from hysop.core.mpi import is_multihost, interhost_comm, host_rank
 
-class TopologyObject(object):
+class TopologyObject(object, metaclass=ABCMeta):
     """
     XML parser base to parse lstopo (hardware info) xml output.
     See hwloc(7) and lstopo(1) man.
     """
 
-    __metaclass__ = ABCMeta
-
     _print_indent = ' '*2
 
     def __init__(self, parent, element, pciids=None):
@@ -181,8 +179,7 @@ class TopologyObject(object):
         self._attributes['distances'] = np.reshape(np.asarray(values, dtype=np.float32),(nbobjs,nbobjs,))
 
 
-class HardwareStatistics(object):
-    __metaclass__ = ABCMeta
+class HardwareStatistics(object, metaclass=ABCMeta):
 
     def _minmax(self, values, op=lambda x: x, dtype=np.int32):
         return 'mean={}, min={}, max={}'.format(op(np.mean(values).astype(dtype)),
diff --git a/hysop/backend/host/fortran/fortran_operator.py b/hysop/backend/host/fortran/fortran_operator.py
index a7a1d68c5..fbf4d5af2 100644
--- a/hysop/backend/host/fortran/fortran_operator.py
+++ b/hysop/backend/host/fortran/fortran_operator.py
@@ -2,7 +2,7 @@
 discrete operators working with fortran.
 
 * :class:`~hysop.backend.host.fortran.FortranOperator` is an abstract class
-    used to provide a common interface to all discrete operators working with 
+    used to provide a common interface to all discrete operators working with
     fortran.
 """
 from abc import ABCMeta
@@ -12,12 +12,11 @@ from hysop.constants import MemoryOrdering
 from hysop.backend.host.host_operator import HostOperator
 from hysop.fields.discrete_field import DiscreteScalarFieldView
 
-class FortranOperator(HostOperator):
+class FortranOperator(HostOperator, metaclass=ABCMeta):
     """
     Abstract class for discrete operators working with fortran.
     """
-    __metaclass__ = ABCMeta
-    
+
     @debug
     def get_field_requirements(self):
         requirements = super(FortranOperator, self).get_field_requirements()
@@ -33,7 +32,7 @@ class FortranOperator(HostOperator):
         if self.discretized:
             return
         super(FortranOperator, self).discretize()
-    
+
     @debug
     def setup(self, work):
         super(FortranOperator, self).setup(work)
diff --git a/hysop/backend/host/host_directional_operator.py b/hysop/backend/host/host_directional_operator.py
index dc4e9816f..f43dccf69 100644
--- a/hysop/backend/host/host_directional_operator.py
+++ b/hysop/backend/host/host_directional_operator.py
@@ -4,15 +4,13 @@ from hysop.tools.decorators  import debug
 from hysop.operator.directional.directional import DirectionalOperatorBase
 from hysop.backend.host.host_operator import HostOperator
 
-class HostDirectionalOperator(DirectionalOperatorBase, HostOperator):
+class HostDirectionalOperator(DirectionalOperatorBase, HostOperator, metaclass=ABCMeta):
     """
     Abstract class for discrete directional operators working on host backends.
-    
-    Field requirements are set such that the current direction will 
+
+    Field requirements are set such that the current direction will
     be contiguous in memory.
     """
-    
-    __metaclass__ = ABCMeta
 
     @debug
     def __init__(self, **kwds):
diff --git a/hysop/backend/host/host_operator.py b/hysop/backend/host/host_operator.py
index bf818f257..8c86415ab 100644
--- a/hysop/backend/host/host_operator.py
+++ b/hysop/backend/host/host_operator.py
@@ -14,11 +14,10 @@ from hysop.core.graph.computational_operator import ComputationalGraphOperator
 from hysop.topology.topology_descriptor import TopologyDescriptor
 
 
-class HostOperator(ComputationalGraphOperator):
+class HostOperator(ComputationalGraphOperator, metaclass=ABCMeta):
     """
     Abstract class for discrete operators working on OpenCL backends.
     """
-    __metaclass__ = ABCMeta
 
     @debug
     def __init__(self, **kwds):
diff --git a/hysop/core/arrays/array.py b/hysop/core/arrays/array.py
index 27a8f7b48..a837463bd 100644
--- a/hysop/core/arrays/array.py
+++ b/hysop/core/arrays/array.py
@@ -7,26 +7,26 @@ from hysop.tools.types import check_instance
 from hysop.tools.numpywrappers import slices_empty
 from hysop.tools.decorators import required_property, optional_property
 
-    
-class Array(object):
+
+class Array(object, metaclass=ABCMeta):
     """
     Interface of an abstract array.
-    An array is a numpy.ndarray work-alike that stores its data and performs 
+    An array is a numpy.ndarray work-alike that stores its data and performs
     its computations on various devices, depending on the backend.
-    All exposed functions should work exactly as in numpy. 
-    Arithmetic methods in Array, when available, should at least support the 
+    All exposed functions should work exactly as in numpy.
+    Arithmetic methods in Array, when available, should at least support the
     broadcasting of scalars.
 
-    For Fortran users, reverse the usual order of indices when accessing elements of an array. 
+    For Fortran users, reverse the usual order of indices when accessing elements of an array.
     to be in line with Python semantics and the natural order of the data.
-    The fact is that Python indexing on lists and other sequences naturally leads to an 
-    outside-to inside ordering (the first index gets the largest grouping, and the last 
+    The fact is that Python indexing on lists and other sequences naturally leads to an
+    outside-to inside ordering (the first index gets the largest grouping, and the last
     gets the smallest element).
-    
+
     See https://docs.scipy.org/doc/numpy-1.10.0/reference/internals.html for more information
     about C versus Fortran ordering in numpy.
-        
-    Numpy notation are used for axes, axe 0 is the slowest varying index and last axe is 
+
+    Numpy notation are used for axes, axe 0 is the slowest varying index and last axe is
     the fastest varying index.
     By default:
         3D C-ordering       is [0,1,2] which corresponds to ZYX transposition state.
@@ -35,13 +35,11 @@ class Array(object):
     This means that when taking array byte strides, in the axis order, the strides are
     decreasing until the last stride wich is the size of array dtype in bytes.
     """
-    
-    __metaclass__ = ABCMeta
-        
+
     def __init__(self, handle, backend, **kwds):
         """
         Build an Array instance.
-        
+
         Parameters
         ----------
         handle:  buffer backend implementation
@@ -51,7 +49,7 @@ class Array(object):
         Notes
         -----
         This should never be called directly by the user.
-        Arrays should be constructed using array backend facilities, like zeros or empty. 
+        Arrays should be constructed using array backend facilities, like zeros or empty.
         The parameters given here refer to a low-level method for instantiating an array.
         """
         from hysop.core.arrays.all import ArrayBackend
@@ -84,7 +82,7 @@ class Array(object):
             msg=msg.format(self.__class__)
             raise RuntimeError(msg)
         return self._backend
-   
+
     handle  = property(get_handle)
     backend = property(get_backend)
 
@@ -111,13 +109,13 @@ class Array(object):
             return self.handle.__nonzero__()
         else:
             return True
-    
+
     @classmethod
     def _not_implemented_yet(cls, funname):
         msg = '{}::{} has not been implemented yet.'
         msg=msg.format(cls.__name__, funname)
         raise NotImplementedError(msg)
-        
+
     @classmethod
     def _unsupported_argument(cls, fname, argname, arg, default_value=None):
         if arg != default_value:
@@ -125,34 +123,34 @@ class Array(object):
             msg+= 'supported and should be set to {}.'
             msg=msg.format(cls.__name__, fname, argname, default_value)
             raise NotImplementedError(msg)
-    
+
     def wrap(self, handle):
         """
         Wrap handle with the same initialization arguments as this instance.
         """
         return self.backend.wrap(handle=handle)
-    
+
     def _call(self, fname, *args, **kargs):
         """
         Calls a handle function.
         """
         f = getattr(self.handle, fname)
         return self.backend._call(f, *args, **kargs)
-    
+
     @abstractmethod
     def as_symbolic_array(self, name, **kwds):
         """
         Return a symbolic array variable that contain a reference to this array.
         """
         pass
-    
+
     @abstractmethod
     def as_symbolic_buffer(self, name, **kwds):
         """
         Return a symbolic buffer variable that contain a reference to this array.
         """
         pass
-    
+
     @abstractmethod
     @required_property
     def get_ndim(self):
@@ -160,7 +158,7 @@ class Array(object):
         Number of array dimensions.
         """
         pass
-    
+
     @abstractmethod
     @required_property
     def get_int_ptr(self):
@@ -182,8 +180,8 @@ class Array(object):
     def set_shape(self):
         """
         Set the shape of this buffer.
-        From the numpy doc: It is not always possible to change the shape of an array without 
-        copying the data. If you want an error to be raised if the data is copied, you should   
+        From the numpy doc: It is not always possible to change the shape of an array without
+        copying the data. If you want an error to be raised if the data is copied, you should
         assign the new shape to the shape attribute of the array.
         """
         pass
@@ -203,7 +201,7 @@ class Array(object):
         Tuple of ints that represents the byte step in each dimension when traversing an array.
         """
         pass
-    
+
     @abstractmethod
     @required_property
     def get_data(self):
@@ -219,7 +217,7 @@ class Array(object):
         Base object if memory is from some other object.
         """
         pass
-   
+
     @abstractmethod
     @required_property
     def get_dtype(self):
@@ -227,21 +225,21 @@ class Array(object):
         numpy.dtype representing the type stored into this buffer.
         """
         pass
-    
+
     @optional_property
     def get_flags(self):
         """
         Information about the memory layout of the array.
         """
         pass
-    
+
     @optional_property
     def get_imag(self):
         """
         The imaginary part of the array.
         """
         pass
-    
+
     @optional_property
     def get_real(self):
         """
@@ -255,7 +253,7 @@ class Array(object):
         An object to simplify the interaction of the array with the ctypes module.
         """
         pass
-    
+
     def get_T(self):
         """
         Same as self.transpose(), except that self is returned if self.ndim < 2.
@@ -264,13 +262,13 @@ class Array(object):
             return self
         else:
             return self.transpose()
-    
+
     def get_size(self):
         """
         Number of elements in the array.
         """
         return prod(self.get_shape())
-    
+
     def get_itemsize(self):
         """
         Number of bytes per element.
@@ -282,8 +280,8 @@ class Array(object):
         Number of bytes in the whole buffer.
         """
         return self.itemsize*self.size
-    
-    
+
+
     # array properties to be (re)defined
     ndim    = property(get_ndim)
     shape   = property(get_shape, set_shape)
@@ -293,19 +291,19 @@ class Array(object):
     base    = property(get_base)
     dtype   = property(get_dtype)
     int_ptr = property(get_int_ptr)
-    
+
     # optional array properties
     flags    = property(get_flags)
     imag     = property(get_imag)
     real     = property(get_real)
     ctypes   = property(get_ctypes)
-    
+
     # deduced array properties, may be redefined
     size     = property(get_size)
     itemsize = property(get_itemsize)
     nbytes   = property(get_nbytes)
     T        = property(get_T)
-    
+
     @abstractmethod
     def get(self, handle=False):
         """
@@ -330,18 +328,18 @@ class Array(object):
         physical memory as other.
         """
         return self.get_data_base() is other.get_data_base()
-    
+
     def ctype(self):
         """
         Equivalent C type corresponding to the numpy.dtype.
         """
         self.__class__.not_implemented_yet('ctype')
-    
+
     def get_order(self):
         """
         Memory ordering.
-        Determine whether the array view is written in C-contiguous order 
-        (last index varies the fastest), or FORTRAN-contiguous order 
+        Determine whether the array view is written in C-contiguous order
+        (last index varies the fastest), or FORTRAN-contiguous order
         in memory (first index varies the fastest).
         If dimension is one, default_order is returned.
         """
@@ -366,7 +364,7 @@ class Array(object):
              Axe 0 is the slowest varying index, last axe is the fastest varying index.
              ie 3D C-ordering       is [2,1,0]
                 3D fortran-ordering is [0,1,2]
-             
+
              Thoses are the axes seen as a numpy view on memory, *only* strides are permutated for access,
              Those axes are found by reverse argsorting the array strides, using a stable sorting algorithm.
 
@@ -416,14 +414,14 @@ class Array(object):
         Copy data from buffer src
         """
         self.backend.memcpy(self, src, **kargs)
-    
+
     def copy_to(self, dst, **kargs):
         """
         Copy data from buffer to dst
         """
         self.backend.memcpy(dst,self, **kargs)
-    
-    
+
+
     def transpose_to_state(self, state, **kargs):
         """
         Transpose buffer to specified transposition state.
@@ -437,7 +435,7 @@ class Array(object):
         for axe in target:
             axes.append(origin.index(axe))
         return self.transpose(axes=axes)
-   
+
 
 
     # np.ndarray like methods
@@ -470,7 +468,7 @@ class Array(object):
         """
         Returns the indices that would partition this array.
         """
-        return self.backend.argpartition(a=self, kth=kth, axis=axis, kind=kind, 
+        return self.backend.argpartition(a=self, kth=kth, axis=axis, kind=kind,
                 order=order, **kargs)
 
     def argsort(self, axis=-1, kind='quicksort', order=None, **kargs):
@@ -478,8 +476,8 @@ class Array(object):
         Returns the indices that would sort this array.
         """
         return self.backend.argsort(a=self, axis=axis, kind=kind, order=order, **kargs)
-    
-    def astype(self, dtype, order=MemoryOrdering.SAME_ORDER, casting='unsafe', subok=True, 
+
+    def astype(self, dtype, order=MemoryOrdering.SAME_ORDER, casting='unsafe', subok=True,
             copy=True, **kargs):
         """
         Copy of the array, cast to a specified type.
@@ -490,7 +488,7 @@ class Array(object):
     def byteswap(self, inplace=False, **kargs):
         """
         Swap the bytes of the array elements
-        Toggle between low-endian and big-endian data representation by returning 
+        Toggle between low-endian and big-endian data representation by returning
         a byteswapped array, optionally swapped in-place.
         """
         return self.backend.byteswap(a=self, inplace=inplace, **kargs)
@@ -519,7 +517,7 @@ class Array(object):
         Complex-conjugate of all elements.
         """
         return self.backend.conj(x=self, out=out, **kargs)
-    
+
     def conjugate(self, out=None, **kargs):
         """
         Return the complex conjugate, element-wise.
@@ -537,7 +535,7 @@ class Array(object):
         Return the cumulative sum of the elements along the given axis.
         """
         return self.backend.cumsum(a=self, axis=axis, dtype=dtype, out=out, **kargs)
-    
+
     def copy(self, order=MemoryOrdering.SAME_ORDER, **kargs):
         """
         Return a copy of the array.
@@ -610,7 +608,7 @@ class Array(object):
         Rearranges the elements in the array in such a way that value of the element i
         in kth position is in the position it would be in a sorted array.
         """
-        return self.backend.partition(a=self, kth=kth, axis=axis, kind=kind, 
+        return self.backend.partition(a=self, kth=kth, axis=axis, kind=kind,
                 order=order, **kargs)
 
     def prod(self, axis=None, dtype=None, out=None, **kargs):
@@ -630,56 +628,56 @@ class Array(object):
         Return a flattened array.
         """
         return self.backend.ravel(a=self, order=order, **kargs)
-    
+
     def repeat(self, repeats, axis=None, **kargs):
         """
         Repeat elements of an array.
         """
         return self.backend.repeat(a=self, repeats=repeats, axis=axis, **kargs)
-    
+
     def reshape(self, new_shape, order=default_order, **kargs):
         """
         Returns an array containing the same data with a new shape.
         """
         return self.backend.reshape(a=self, newshape=new_shape, order=order, **kargs)
-    
+
     def resize(self, new_shape, refcheck=True, **kargs):
         """
         Change shape and size of array in-place.
         """
         return self.backend.resize(a=self, new_shape=new_shape, refcheck=refcheck, **kargs)
-    
+
     def round(self, decimals=0, out=None, **kargs):
         """
         Return a with each element rounded to the given number of decimals.
         """
         return self.backend.around(a=self, decimals=decimals, out=out, **kargs)
-    
+
     def searchsorted(self, v, side='left', sorter=None, **kargs):
         """
         Find indices where elements of v should be inserted in a to maintain order.
         """
         return self.backend.searchsorted(a=self, v=v, side=side, sorter=sorter, **kargs)
-    
+
     def sort(self, axis=-1, kind='quicksort', order=None, **kargs):
         """
         Sort an array, in-place.
         """
         return self.backend.sort(a=self, axis=axis, kind=kind, order=order, **kargs)
-    
+
     def squeeze(self, axis=None, **kargs):
         """
         Remove single-dimensional entries from the shape of a.
         """
         return self.backend.squeeze(a=self, axis=axis, **kargs)
-    
+
     def std(self, axis=None, dtype=None, out=None, ddof=0, **kargs):
         """
         Returns the standard deviation of the array elements along given axis.
         """
-        return self.backend.std(a=self, axis=axis, dtype=dtype, out=out, 
+        return self.backend.std(a=self, axis=axis, dtype=dtype, out=out,
                 ddof=ddof)
-    
+
     def sum(self, axis=None, dtype=None, out=None, **kargs):
         """
         Return the sum of the array elements over the given axis.
@@ -691,18 +689,18 @@ class Array(object):
         Return a view of the array with axis1 and axis2 interchanged.
         """
         return self.backend.swapaxes(axis1=axis1, axis2=axis2, **kargs)
-    
+
     def trace(self, offset=0, axis1=0, axis2=1, dtype=None, out=None, **kargs):
         """
         Return the sum along diagonals of the array.
         """
-        return self.backend.trace(a=self, offset=offset, 
+        return self.backend.trace(a=self, offset=offset,
                 axis1=axis1, axis2=axis2, dtype=dtype, out=out, **kargs)
-    
+
     def transpose(self, axes=None, **kargs):
         """
         Returns a view of the array with axes transposed.
-        """ 
+        """
         return self.backend.transpose(a=self, axes=axes, **kargs)
 
     def var(self, axis=None, dtype=None, out=None, ddof=0, **kargs):
@@ -710,7 +708,7 @@ class Array(object):
         Returns the variance of the array elements, along given axis.
         """
         return self.backend.var(a=self, axis=axis, dtype=dtype, out=out, ddof=ddof, **kargs)
-   
+
 
     ## Array restricted methods
     def setflags(self, write=None, align=None, uic=None):
@@ -720,13 +718,13 @@ class Array(object):
         msg='{}::set_flags() should not be called.'
         msg=msg.format(self.__class__.__name__)
         raise RuntimeError(msg)
-   
+
 
     ## Array specific unimplemented methods
     def tofile(self, fid, sep='', format='%s', **kargs):
         """
         Write array to a file as text or binary (default).
-        This is a convenience function for quick storage of array data. 
+        This is a convenience function for quick storage of array data.
         Information on endianness and precision is lost.
         """
         self.__class__.not_implemented_yet('tofile')
@@ -780,7 +778,7 @@ class Array(object):
         New view of array with the same data.
         """
         self._not_implemented_yet('view')
-    def astype(self, dtype, order=MemoryOrdering.SAME_ORDER, 
+    def astype(self, dtype, order=MemoryOrdering.SAME_ORDER,
                      casting='unsafe', subok=True, copy=True, **kargs):
         """
         Copy of the array, cast to a specified type.
@@ -793,7 +791,7 @@ class Array(object):
         """
         self._not_implemented_yet('tobytes')
 
-   
+
    # logical operators
     def __eq__(self, other):
         return self.backend.equal(self, other)
@@ -830,7 +828,7 @@ class Array(object):
         return self.backend.divide(self, other)
     def __mod__(self, other):
         return self.backend.mod(self, other)
-    
+
     def __and__ (self, other):
         return self.backend.bitwise_and(self,other)
     def __xor__ (self, other):
@@ -856,7 +854,7 @@ class Array(object):
         return self.backend.divide(other, self)
     def __rmod__(self, other):
         return self.backend.mod(other, self)
-    
+
     def __rand__ (other, self):
         return self.backend.bitwise_and(other, self)
     def __rxor__ (other, self):
@@ -867,7 +865,7 @@ class Array(object):
         return self.backend.left_shift(other, self)
     def __rrshift__ (other, self):
         return self.backend.right_shift(other, self)
-    
+
     def __iadd__(self, other):
         return self.backend.add(self, other, out=self)
     def __isub__(self, other):
@@ -883,7 +881,7 @@ class Array(object):
     def __imod__(self, other):
         return self.backend.mod(self, other, out=self)
 
-    
+
     def __str__(self):
         return self._handle.__str__()
     def __repr__(self):
diff --git a/hysop/core/arrays/array_backend.py b/hysop/core/arrays/array_backend.py
index 82b3812be..07fb46176 100644
--- a/hysop/core/arrays/array_backend.py
+++ b/hysop/core/arrays/array_backend.py
@@ -11,7 +11,7 @@ from hysop.tools.numerics import is_fp, is_complex, match_float_type, \
                                  match_complex_type, complex_to_float_dtype
 from hysop.core.memory.allocator import AllocatorBase
 
-class ArrayBackend(TaggedObject):
+class ArrayBackend(TaggedObject, metaclass=ABCMeta):
     """
     Interface of an abstract array backend.
     An array backend is a numpy work-alike collection of functions that
@@ -69,8 +69,6 @@ class ArrayBackend(TaggedObject):
     explicit message through the _not_implemented_yet method.
     """
 
-    __metaclass__ = ABCMeta
-
     __registered_backends = {}
     """
     Contains all registered backends.
diff --git a/hysop/core/graph/allocator.py b/hysop/core/graph/allocator.py
index c8d3f7a24..08cbe0b85 100644
--- a/hysop/core/graph/allocator.py
+++ b/hysop/core/graph/allocator.py
@@ -6,8 +6,7 @@ from hysop.tools.types import check_instance
 from hysop.core.arrays.all import ArrayBackend, HostArrayBackend, OpenClArrayBackend
 
 
-class MemoryRequestsAllocator(object):
-    __metaclass__ = ABCMeta
+class MemoryRequestsAllocator(object, metaclass=ABCMeta):
 
     @classmethod
     @not_implemented
diff --git a/hysop/core/graph/computational_graph.py b/hysop/core/graph/computational_graph.py
index 7f5226ffb..d8a5eba08 100644
--- a/hysop/core/graph/computational_graph.py
+++ b/hysop/core/graph/computational_graph.py
@@ -20,13 +20,11 @@ from hysop.core.mpi import main_rank
 from abc import ABCMeta, abstractmethod
 
 
-class ComputationalGraph(ComputationalGraphNode):
+class ComputationalGraph(ComputationalGraphNode, metaclass=ABCMeta):
     """
     Interface of an abstract graph of continuous operators (ie. a computational graph).
     """
 
-    __metaclass__ = ABCMeta
-
     __FORCE_REPORTS__ = False
 
     @debug
diff --git a/hysop/core/graph/computational_node.py b/hysop/core/graph/computational_node.py
index 160db9b45..bd394fa06 100644
--- a/hysop/core/graph/computational_node.py
+++ b/hysop/core/graph/computational_node.py
@@ -49,13 +49,11 @@ def topology_handled(f):
     return _check
 
 
-class ComputationalGraphNode(OperatorBase):
+class ComputationalGraphNode(OperatorBase, metaclass=ABCMeta):
     """
     Interface of an abstract computational graph node.
     """
 
-    __metaclass__ = ABCMeta
-
     @debug
     def __init__(self, input_fields=None, output_fields=None,
                  input_params=None, output_params=None,
diff --git a/hysop/core/graph/computational_operator.py b/hysop/core/graph/computational_operator.py
index b71d3bc4c..afccabccc 100644
--- a/hysop/core/graph/computational_operator.py
+++ b/hysop/core/graph/computational_operator.py
@@ -9,7 +9,7 @@ from hysop.topology.topology_descriptor import TopologyDescriptor
 from hysop.fields.field_requirements import DiscreteFieldRequirements
 from abc import ABCMeta
 
-class ComputationalGraphOperator(ComputationalGraphNode):
+class ComputationalGraphOperator(ComputationalGraphNode, metaclass=ABCMeta):
     """
     Interface of an abstract computational graph operator.
     An operator is a single graph node with its own inputs and outputs.
@@ -119,8 +119,6 @@ class ComputationalGraphOperator(ComputationalGraphNode):
     or at least, a child class of hysop.core.graph.computational_graph.ComputationalGraph.
     """
 
-    __metaclass__ = ABCMeta
-
     @debug
     def __init__(self, input_fields=None, output_fields=None, **kwds):
         """
diff --git a/hysop/core/graph/continuous.py b/hysop/core/graph/continuous.py
index ae2e358bc..75a4d5ec9 100755
--- a/hysop/core/graph/continuous.py
+++ b/hysop/core/graph/continuous.py
@@ -17,16 +17,15 @@ from hysop.parameters.parameter import Parameter
 
 import hysop.tools.io_utils as io
 
-class OperatorBase(TaggedObject):
+class OperatorBase(TaggedObject, metaclass=ABCMeta):
     """
     Abstract interface to continuous operators.
     """
-    __metaclass__ = ABCMeta
 
     @debug
     def __init__(self, name, fields, tensor_fields, parameters,
-                       mpi_params=None, 
-                       io_params=False, 
+                       mpi_params=None,
+                       io_params=False,
                        **kwds):
         """
         Parameters
@@ -56,7 +55,7 @@ class OperatorBase(TaggedObject):
         """
         super(OperatorBase,self).__init__(tagged_cls=OperatorBase, tag_prefix='node',
                                           **kwds)
-        
+
         check_instance(fields, tuple, values=ScalarField)
         check_instance(tensor_fields, tuple, values=TensorField)
         check_instance(parameters, tuple, values=Parameter)
@@ -66,7 +65,7 @@ class OperatorBase(TaggedObject):
         for tfield in tensor_fields:
             for field in tfield:
                 assert field in fields
-        
+
         self.name       = name
         self.fields     = fields
         self.tensor_fields = tensor_fields
@@ -80,14 +79,14 @@ class OperatorBase(TaggedObject):
     def _get_profiling_info(self):
         """Collect profiling informations of profiled attributes."""
         pass
-    
+
     def profiler_report(self):
         """Update profiler statistics and print report."""
         if not __PROFILE__:
             return
         self._profiler.summarize()
         print(str(self._profiler))
-    
+
     def _set_io(self):
         """
         Initialize the io params.
@@ -118,7 +117,7 @@ class OperatorBase(TaggedObject):
         """
         Initialize the mpi context, depending on local fields, domain
         and so on.
-        
+
         Notes
         -----
         This function is private and must not be called by
@@ -127,22 +126,22 @@ class OperatorBase(TaggedObject):
         """
         if len(self.fields) > 0:
             self.domain = self.fields[0].domain
-            
+
             # Check if all fields have the same domain
             for field in self.fields:
                 assert field.domain is self.domain, 'All fields of the operator\
                 must be defined on the same domain.'
-            
+
             # Set/check mpi context
             if (self.mpi_params is None):
                 self.mpi_params = MPIParams(comm=self.domain.task_comm,
                                        task_id=self.domain.current_task())
-            
+
         else:
             if (self.mpi_params is None):
                 self.mpi_params = default_mpi_params()
             self.domain = None
-        
+
         self._profiler = Profiler(obj=self, comm=self.mpi_params.comm)
 
     def short_description(self):
diff --git a/hysop/core/graph/node_generator.py b/hysop/core/graph/node_generator.py
index 070e8902f..402f61fac 100644
--- a/hysop/core/graph/node_generator.py
+++ b/hysop/core/graph/node_generator.py
@@ -4,13 +4,11 @@ from hysop.tools.decorators import debug
 from hysop.tools.types import first_not_None
 from hysop.core.graph.computational_node import ComputationalGraphNode
 
-class ComputationalGraphNodeGenerator(object):
+class ComputationalGraphNodeGenerator(object, metaclass=ABCMeta):
     """
     A class that can generate multiple hysop.core.graph.ComputationalGraphNode.
     """
 
-    __metaclass__ = ABCMeta
-
     @debug
     def __init__(self, candidate_input_tensors, candidate_output_tensors,
             name=None, pretty_name=None,
diff --git a/hysop/core/memory/allocator.py b/hysop/core/memory/allocator.py
index 098af43f7..4ef44783f 100644
--- a/hysop/core/memory/allocator.py
+++ b/hysop/core/memory/allocator.py
@@ -7,13 +7,11 @@ from hysop.tools.units import bytes2str
 from hysop.tools.types import first_not_None
 from hysop.tools.handle import TaggedObject
 
-class AllocatorBase(TaggedObject):
+class AllocatorBase(TaggedObject, metaclass=ABCMeta):
     """
     Base class for allocators.
     """
 
-    __metaclass__=ABCMeta
-
     is_deferred=False
 
     def __init__(self, verbose, **kwds):
diff --git a/hysop/core/memory/mempool.py b/hysop/core/memory/mempool.py
index b3dcd7c9e..7b0ffa361 100644
--- a/hysop/core/memory/mempool.py
+++ b/hysop/core/memory/mempool.py
@@ -24,13 +24,11 @@ else:
         return p-1
 
 
-class MemoryPool(object):
+class MemoryPool(object, metaclass=ABCMeta):
     """
     pyopencl/pycuda like memory pool extended to be compatible for all backends.
     """
 
-    __metaclass__ = ABCMeta
-
     def __init__(self, name, allocator, max_alloc_bytes=None,
                 mantissa_bits=4, verbose=None, **kwds):
         """
diff --git a/hysop/domain/domain.py b/hysop/domain/domain.py
index 277c95371..a7f696db4 100644
--- a/hysop/domain/domain.py
+++ b/hysop/domain/domain.py
@@ -15,9 +15,8 @@ from hysop.symbolic.frame import SymbolicFrame
 from hysop.deps import hashlib, np
 
 
-class DomainView(TaggedObjectView):
+class DomainView(TaggedObjectView, metaclass=ABCMeta):
     """Abstract base class for views on domains. """
-    __metaclass__ = ABCMeta
 
     __slots__ = ('_domain', '_topology_state')
 
@@ -169,9 +168,8 @@ class DomainView(TaggedObjectView):
     frame = property(_get_frame)
 
 
-class Domain(RegisteredObject):
+class Domain(RegisteredObject, metaclass=ABCMeta):
     """Abstract base class for the description of physical domains. """
-    __metaclass__ = ABCMeta
 
     @debug
     def __new__(cls, dim, parent_comm=None, proc_tasks=None, **kwds):
diff --git a/hysop/fields/discrete_field.py b/hysop/fields/discrete_field.py
index 1490a15a8..aafa62d99 100644
--- a/hysop/fields/discrete_field.py
+++ b/hysop/fields/discrete_field.py
@@ -23,18 +23,16 @@ from hysop.fields.continuous_field import Field, VectorField, TensorField, \
 from hysop.domain.domain import DomainView
 from hysop.mesh.mesh import MeshView
 
-class DiscreteScalarFieldViewContainerI(object):
+class DiscreteScalarFieldViewContainerI(object, metaclass=ABCMeta):
     """
     Common abstract interface for scalar and tensor-like container of
     discrete field views.
     """
 
-    __metaclass__ = ABCMeta
-
     @debug
     def __new__(cls, **kwds):
         return super(DiscreteScalarFieldViewContainerI, cls).__new__(cls, **kwds)
-    
+
     @property
     def is_scalar(self):
         return (not self.is_tensor)
@@ -73,7 +71,7 @@ class DiscreteScalarFieldViewContainerI(object):
         but including duplicate fields.
         """
         return len(self.discrete_field_views())
-    
+
     def ids_to_components(self, ids):
         """Convert tensor coordinates into 1d offsets."""
         check_instance(ids, tuple, values=(int,tuple), allow_none=True)
@@ -412,13 +410,11 @@ class DiscreteScalarFieldViewContainerI(object):
         return self.long_description()
 
 
-class DiscreteScalarFieldView(DiscreteScalarFieldViewContainerI, TaggedObjectView, VariableTag):
+class DiscreteScalarFieldView(DiscreteScalarFieldViewContainerI, TaggedObjectView, VariableTag, metaclass=ABCMeta):
     """
     View over a DiscreteScalarField (taking into account a topology state).
     """
 
-    __metaclass__ = ABCMeta
-
     __slots__ = ('_dfield', '_topology_state', '_topology_view', '_symbol')
 
     @property
@@ -479,7 +475,7 @@ class DiscreteScalarFieldView(DiscreteScalarFieldViewContainerI, TaggedObjectVie
         return (obj is self)
     def __getitem__(self, slc):
         return self
-    
+
     def discrete_field_views(self):
         return (self,)
 
@@ -600,7 +596,7 @@ class DiscreteScalarFieldView(DiscreteScalarFieldViewContainerI, TaggedObjectVie
     memory_request_id = property(_get_memory_request_id)
 
 
-class DiscreteScalarField(NamedScalarContainerI, TaggedObject):
+class DiscreteScalarField(NamedScalarContainerI, TaggedObject, metaclass=ABCMeta):
     """
     Discrete representation of scalar or vector fields,
 
@@ -618,8 +614,6 @@ class DiscreteScalarField(NamedScalarContainerI, TaggedObject):
     depending on the discrete field topology backend and the ghost exchange strategy.
     """
 
-    __metaclass__ = ABCMeta
-
     @debug
     def __new__(cls, field, topology, register_discrete_field=True,
                 name=None, pretty_name=None,
@@ -728,7 +722,7 @@ class DiscreteTensorField(NamedTensorContainerI, DiscreteScalarFieldViewContaine
     Is also garanties that all fields shares the same domain, but contained
     discrete fields may be defined on different topologies.
     """
-    
+
     @property
     def is_tensor(self):
         return True
diff --git a/hysop/fields/ghost_exchangers.py b/hysop/fields/ghost_exchangers.py
index 75087be12..014c3e6ba 100644
--- a/hysop/fields/ghost_exchangers.py
+++ b/hysop/fields/ghost_exchangers.py
@@ -106,9 +106,8 @@ class  LocalBoundaryExchanger(object):
         return exchange_ghosts
 
 
-class GhostExchangerI(object):
+class GhostExchangerI(object, metaclass=ABCMeta):
     """Abstract interface for a ghost exchanger."""
-    __metaclass__ = ABCMeta
 
     @abstractmethod
     def exchange_ghosts(self, **kwds):
diff --git a/hysop/mesh/mesh.py b/hysop/mesh/mesh.py
index 4f30aa600..2b7993d73 100644
--- a/hysop/mesh/mesh.py
+++ b/hysop/mesh/mesh.py
@@ -17,16 +17,15 @@ from hysop.tools.decorators import debug
 from hysop.tools.types import check_instance
 from hysop.tools.handle import TaggedObject, TaggedObjectView
 
-class MeshView(TaggedObjectView):
+class MeshView(TaggedObjectView, metaclass=ABCMeta):
     """Abstract base class for views on meshes. """
-    __metaclass__ = ABCMeta
 
     __slots__ = ('_mesh', '_topology_state')
-    
+
     @debug
     def __new__(cls, mesh, topology_state, **kwds):
         return super(MeshView, cls).__new__(cls, obj_view=mesh, **kwds)
-    
+
     @debug
     def __init__(self, mesh, topology_state, **kwds):
         """Initialize a MeshView."""
@@ -39,11 +38,11 @@ class MeshView(TaggedObjectView):
     def _get_mesh(self):
         """Return the original mesh on which the view is."""
         return self._mesh
-    
+
     def _get_topology_state(self):
         """Return the topology state"""
         return self._topology_state
-    
+
     def _get_dim(self):
         """Return the dimension of the domain."""
         return self._mesh._topology.domain.dim
@@ -61,30 +60,29 @@ class MeshView(TaggedObjectView):
     def __str__(self):
         """Equivalent to self.long_description()"""
         return self.long_description()
-        
+
     mesh = property(_get_mesh)
     topology_state = property(_get_topology_state)
     dim = property(_get_dim)
 
-class Mesh(TaggedObject):
+class Mesh(TaggedObject, metaclass=ABCMeta):
     """Abstract base class for local to process meshes."""
-    __metaclass__ = ABCMeta
 
     def __new__(cls, topology, **kwds):
         return super(Mesh, cls).__new__(cls, **kwds)
-    
+
     @debug
     def __init__(self, topology, **kwds):
         """Initialize a mesh."""
         check_instance(topology, Topology)
         super(Mesh, self).__init__(tag_prefix='m', **kwds)
         self._topology = topology
-    
+
     @abstractmethod
     def view(self, topology_state):
         """Return a view on this mesh using a topology state."""
         pass
-    
+
     def _get_topology(self):
         """Return a topology view on the original topology that defined this mesh."""
         return self._topology
diff --git a/hysop/numerics/fft/fft.py b/hysop/numerics/fft/fft.py
index 494fb6550..c54495669 100644
--- a/hysop/numerics/fft/fft.py
+++ b/hysop/numerics/fft/fft.py
@@ -89,7 +89,7 @@ class FFTQueueI(object):
     def execute(self, wait_for=None):
         """Execute all planned plans."""
         pass
-    
+
     @abstractmethod
     def __iadd__(self, *plans):
         """Add a plan to the queue."""
@@ -100,13 +100,12 @@ class FFTQueueI(object):
         return self.execute(**kwds)
 
 
-class FFTPlanI(object):
+class FFTPlanI(object, metaclass=ABCMeta):
     """
     Common inteface for FFT plans.
     Basically just a functor that holds relevant data
     to execute a preconfigurated FFT-like tranform.
     """
-    __metaclass__ = ABCMeta
 
     def __init__(self, verbose=__VERBOSE__):
         self.verbose = verbose
@@ -119,14 +118,14 @@ class FFTPlanI(object):
         Return currently planned input array.
         """
         pass
-    
+
     @abstractmethod
     def output_array(self):
         """
         Return currently planned output array.
         """
         pass
-    
+
     def setup(self, queue=None):
         """
         Method that has to be called before any call to execute.
@@ -136,11 +135,11 @@ class FFTPlanI(object):
             raise RuntimeError(msg)
         self._setup = True
         return self
-    
+
     @property
     def required_buffer_size(self):
         """
-        Return the required temporary buffer size in bytes to 
+        Return the required temporary buffer size in bytes to
         compute the transform.
         """
         assert self._setup
@@ -151,7 +150,7 @@ class FFTPlanI(object):
         assert self._setup
         assert not self._allocated
         self._allocated = True
-    
+
 
     @abstractmethod
     def execute(self):
@@ -171,7 +170,7 @@ class FFTPlanI(object):
         self.execute(**kwds)
 
 
-class FFTI(object):
+class FFTI(object, metaclass=ABCMeta):
     """
     Interface to compute local to process FFT-like transforms.
     Common inteface for all array backends, based on the numpy.fft interface.
@@ -194,15 +193,15 @@ class FFTI(object):
     Other R2R transforms:
         DCT-IV and DCT-IV are only supported by the FFTW backend at this time.
         DCT-V to DCT-VIII and DST-V to DST-VII are not supported by any FFT backend.
-    
+
     About floating point precision:
-        By default, both simple and double precision are supported. 
+        By default, both simple and double precision are supported.
         numpy only supports double precision (simple precision is supported by casting).
         FFTW also supports long double precision.
-    
+
     Normalization:
         The default normalization has the direct transforms unscaled and the inverse transform
-        is scaled by 1/N where N is the logical size of the transform. 
+        is scaled by 1/N where N is the logical size of the transform.
         N should not to be confused with the physical size of the input arrays n:
 
         FFT, RFFT:               N = n
@@ -240,8 +239,7 @@ class FFTI(object):
         *Zero fill
     Those methods will be used by the n-dimensional planner.
     """
-    __metaclass__ = ABCMeta
-        
+
     __transform2fn = {
         TransformType.FFT:      ('fft',   {}),
         TransformType.IFFT:     ('ifft',  {}),
@@ -264,9 +262,9 @@ class FFTI(object):
         TransformType.IDST_III: ('idst',  {'type': 3}),
         TransformType.IDST_IV:  ('idst',  {'type': 4}),
     }
-    
+
     @classmethod
-    def default_interface_from_backend(cls, backend, 
+    def default_interface_from_backend(cls, backend,
             enable_opencl_host_buffer_mapping, **kwds):
         check_instance(backend, ArrayBackend)
         if (backend.kind is Backend.HOST):
@@ -296,7 +294,7 @@ class FFTI(object):
                 msg='Backend mismatch {} vs {}.'
                 msg=msg.format(self.backend, backend)
                 raise RuntimeError(msg)
-    
+
     def get_transform(self, transform):
         check_instance(transform, TransformType)
         if (transform not in self.__transform2fn):
@@ -308,7 +306,7 @@ class FFTI(object):
             fn = functools.partial(fn, **fkwds)
         return fn
 
-    def __init__(self, backend, 
+    def __init__(self, backend,
             warn_on_allocation=True,
             error_on_allocation=False):
         """Initializes the interface and default supported real and complex types."""
@@ -325,7 +323,7 @@ class FFTI(object):
         self.backend = backend
         self.warn_on_allocation  = warn_on_allocation
         self.error_on_allocation = error_on_allocation
-   
+
     def allocate_output(self, out, shape, dtype):
         """Alocate output if required and check shape and dtype."""
         if (out is None):
@@ -342,7 +340,7 @@ class FFTI(object):
             assert out.dtype == dtype
             assert out.shape == shape
         return out
-    
+
     @classmethod
     def default_interface(cls, **kwds):
         """Get the default FFT interface."""
@@ -399,17 +397,17 @@ class FFTI(object):
         out: array_like of np.complex64 or np.complex128
             Complex output array of the same shape and dtype as the input.
         axis: int, optional
-            Axis over witch to compute the FFT. 
+            Axis over witch to compute the FFT.
             Defaults to last axis.
 
         Returns
         -------
         (shape, dtype) of the output array determined from the input array.
-        
+
         Notes
         -----
         N = a.shape[axis]
-        out[0] will contain the sum of the signal (zero-frequency term always real for 
+        out[0] will contain the sum of the signal (zero-frequency term always real for
         real inputs).
 
         If N is even:
@@ -439,9 +437,9 @@ class FFTI(object):
         out: array_like of np.complex64 or np.complex128
             Complex output array of the same shape and dtype as the input.
         axis: int, optional
-            Axis over witch to compute the FFT. 
+            Axis over witch to compute the FFT.
             Defaults to last axis.
-        
+
         Returns
         -------
         (shape, dtype, logical_size) of the output array determined from the input array.
@@ -456,7 +454,7 @@ class FFTI(object):
     @abstractmethod
     def rfft(self, a, out, axis=-1, **kwds):
         """
-        Compute the unscaled one-dimensional real to hermitian complex discrete Fourier 
+        Compute the unscaled one-dimensional real to hermitian complex discrete Fourier
         Transform.
 
         Parameters
@@ -468,17 +466,17 @@ class FFTI(object):
             out.shape[...]  = a.shape[...]
             out.shape[axis] = a.shape[axis]//2 + 1
         axis: int, optional
-            Axis over witch to compute the transform. 
+            Axis over witch to compute the transform.
             Defaults to last axis.
-        
+
         Returns
         -------
         (shape, dtype) of the output array determined from the input array.
-        
+
         Notes
         -----
-        For real inputs there is no information in the negative frequency components that 
-        is not already  available from the positive frequency component because of the 
+        For real inputs there is no information in the negative frequency components that
+        is not already  available from the positive frequency component because of the
         Hermitian symmetry.
 
         N = out.shape[axis] = a.shape[axis]//2 + 1
@@ -504,7 +502,7 @@ class FFTI(object):
     @abstractmethod
     def irfft(self, a, out, n=None, axis=-1, **kwds):
         """
-        Compute the one-dimensional hermitian complex to real discrete Fourier Transform 
+        Compute the one-dimensional hermitian complex to real discrete Fourier Transform
         scaled by 1/N.
 
         Parameters
@@ -521,22 +519,22 @@ class FFTI(object):
             Length of the transformed axis of the output.
             ie: n should be in [2*(a.shape[axis]-1), 2*(a.shape[axis]-1)+1]
         axis: int, optional
-            Axis over witch to compute the transform. 
+            Axis over witch to compute the transform.
             Defaults to last axis.
-        
+
         Notes
         -----
         To get an odd number of output points, n or out must be specified.
-        
+
         Returns
         -------
-        (shape, dtype, logical_size) of the output array determined from the input array, 
+        (shape, dtype, logical_size) of the output array determined from the input array,
         out and n.
         """
         assert a.dtype   in self.supported_ctypes
         cshape = a.shape
         rtype  = complex_to_float_dtype(a.dtype)
-        
+
         rshape_even, rshape_odd = list(a.shape), list(a.shape)
         rshape_even[axis] = 2*(cshape[axis]-1)
         rshape_odd[axis]  = 2*(cshape[axis]-1) + 1
@@ -561,12 +559,12 @@ class FFTI(object):
             n = rshape[axis]
         else:
             rshape = rshape_odd
-        
+
         rshape = tuple(rshape)
         logical_size = n
         assert rshape[axis] == logical_size
         return (rshape, rtype, logical_size)
-    
+
     @abstractmethod
     def dct(self, a, out=None, type=2, axis=-1, **kwds):
         """
@@ -579,7 +577,7 @@ class FFTI(object):
         out: array_like
             Real output array of matching input type and shape.
         axis: int, optional
-            Axis over witch to compute the transform. 
+            Axis over witch to compute the transform.
             Defaults to last axis.
         Returns
         -------
@@ -591,13 +589,13 @@ class FFTI(object):
             assert a.dtype == out.dtype
             assert np.array_equal(a.shape, out.shape)
         return (a.shape, a.dtype)
-    
+
     @abstractmethod
     def idct(self, a, out=None, type=2, axis=-1, **kwds):
         """
         Compute the one-dimensional Inverse Cosine Transform of specified type.
-        
-        Default scaling is 1/(2*N)   for IDCT type (2,3,4) and 
+
+        Default scaling is 1/(2*N)   for IDCT type (2,3,4) and
                            1/(2*N-2) for IDCT type 1.
 
         Parameters
@@ -607,11 +605,11 @@ class FFTI(object):
         out: array_like
             Real output array of matching input type and shape.
         axis: int, optional
-            Axis over witch to compute the transform. 
+            Axis over witch to compute the transform.
             Defaults to last axis.
         Returns
         -------
-        (shape, dtype, inverse_type, logical_size) of the output array determined from the input 
+        (shape, dtype, inverse_type, logical_size) of the output array determined from the input
         array.
         """
         itype = [1,3,2,4][type-1]
@@ -624,7 +622,7 @@ class FFTI(object):
             assert a.dtype == out.dtype
             assert np.array_equal(a.shape, out.shape)
         return (a.shape, a.dtype, itype, logical_size)
-    
+
     @abstractmethod
     def dst(self, a, out=None, type=2, axis=-1, **kwds):
         """
@@ -637,7 +635,7 @@ class FFTI(object):
         out: array_like
             Real output array of matching input type and shape.
         axis: int, optional
-            Axis over witch to compute the transform. 
+            Axis over witch to compute the transform.
             Defaults to last axis.
         Returns
         -------
@@ -655,7 +653,7 @@ class FFTI(object):
         """
         Compute the one-dimensional Inverse Sine Transform of specified type.
 
-        Default scaling is 1/(2*N)   for IDST type (2,3,4) and 
+        Default scaling is 1/(2*N)   for IDST type (2,3,4) and
                            1/(2*N+2) for IDST type 1.
 
         Parameters
@@ -665,11 +663,11 @@ class FFTI(object):
         out: array_like
             Real output array of matching input type and shape.
         axis: int, optional
-            Axis over witch to compute the transform. 
+            Axis over witch to compute the transform.
             Defaults to last axis.
         Returns
         -------
-        (shape, dtype, inverse_type, logical_size) of the output array determined from the input 
+        (shape, dtype, inverse_type, logical_size) of the output array determined from the input
         array.
         """
         itype = [1,3,2,4][type-1]
@@ -692,7 +690,7 @@ class FFTI(object):
     def plan_copy(self, tg, src, dst):
         """Plan a copy from src to dst."""
         pass
-    
+
     @abstractmethod
     def plan_accumulate(self, tg, src, dst):
         """Plan an accumulation from src into dst."""
@@ -707,7 +705,7 @@ class FFTI(object):
     def plan_fill_zeros(self, tg, a, slices):
         """Plan to fill every input slices of input array a with zeroes."""
         pass
-    
+
     @abstractmethod
     def plan_compute_energy(self, tg, fshape, src, dst, transforms, mutexes=None):
         """Plan to compute energy from src to energy."""
diff --git a/hysop/operator/adapt_timestep.py b/hysop/operator/adapt_timestep.py
index b547e6538..65e91de33 100755
--- a/hysop/operator/adapt_timestep.py
+++ b/hysop/operator/adapt_timestep.py
@@ -15,8 +15,7 @@ from hysop.fields.continuous_field import Field
 from hysop.parameters import ScalarParameter, TensorParameter
 from hysop.core.mpi import MPI
 
-class TimestepCriteria(ComputationalGraphOperator):
-    __metaclass__ = ABCMeta
+class TimestepCriteria(ComputationalGraphOperator, metaclass=ABCMeta):
 
     @debug
     def __init__(self, parameter, input_params, output_params,
diff --git a/hysop/operator/base/custom_symbolic_operator.py b/hysop/operator/base/custom_symbolic_operator.py
index 408432613..bba8012df 100644
--- a/hysop/operator/base/custom_symbolic_operator.py
+++ b/hysop/operator/base/custom_symbolic_operator.py
@@ -1264,11 +1264,10 @@ class SymbolicExpressionParser(object):
         i = symbols.index(expr)
         return symbols[axes[i]]
 
-class CustomSymbolicOperatorBase(DirectionalOperatorBase):
+class CustomSymbolicOperatorBase(DirectionalOperatorBase, metaclass=ABCMeta):
     """
     Common implementation interface for custom symbolic (code generated) operators.
     """
-    __metaclass__ = ABCMeta
 
     __default_method = {
             ComputeGranularity: 0,
diff --git a/hysop/operator/base/derivative.py b/hysop/operator/base/derivative.py
index 834416f58..5b860814b 100644
--- a/hysop/operator/base/derivative.py
+++ b/hysop/operator/base/derivative.py
@@ -17,13 +17,11 @@ from hysop.fields.continuous_field import Field, ScalarField
 from hysop.operator.base.spectral_operator import SpectralOperatorBase
 
 
-class SpaceDerivativeBase(object):
+class SpaceDerivativeBase(object, metaclass=ABCMeta):
     """
     Common implementation interface for derivative operators.
     """
 
-    __metaclass__ = ABCMeta
-
     @debug
     def __init__(self, F, dF, A=None,
             derivative=None, direction=None,
diff --git a/hysop/operator/base/enstrophy.py b/hysop/operator/base/enstrophy.py
index 1305461ca..f2c401b05 100644
--- a/hysop/operator/base/enstrophy.py
+++ b/hysop/operator/base/enstrophy.py
@@ -9,13 +9,11 @@ from hysop.core.memory.memory_request import MemoryRequest
 from hysop.topology.cartesian_descriptor import CartesianTopologyDescriptors
 from hysop.parameters.scalar_parameter import ScalarParameter
 
-class EnstrophyBase(object):
+class EnstrophyBase(object, metaclass=ABCMeta):
     """
     Common implementation interface for enstrophy.
     """
 
-    __metaclass__ = ABCMeta
-
     @debug
     def __init__(self, vorticity, enstrophy, WdotW, rho, rho_0,
                     variables, name=None, pretty_name=None, **kwds):
diff --git a/hysop/operator/base/external_force.py b/hysop/operator/base/external_force.py
index 7cfb01798..59d9cb5ad 100644
--- a/hysop/operator/base/external_force.py
+++ b/hysop/operator/base/external_force.py
@@ -15,9 +15,8 @@ from hysop.parameters.tensor_parameter import TensorParameter
 from hysop.parameters.scalar_parameter import ScalarParameter
 from hysop.tools.interface import NamedObjectI
 
-class ExternalForce(NamedObjectI):
+class ExternalForce(NamedObjectI, metaclass=ABCMeta):
     """Interface to implement a custom external force."""
-    __metaclass__ = ABCMeta
 
     def __init__(self, name, dim, Fext, **kwds):
         super(ExternalForce, self).__init__(name=name, **kwds)
diff --git a/hysop/operator/base/integrate.py b/hysop/operator/base/integrate.py
index bfbda8177..2a9533557 100644
--- a/hysop/operator/base/integrate.py
+++ b/hysop/operator/base/integrate.py
@@ -10,13 +10,11 @@ from hysop.topology.cartesian_descriptor import CartesianTopologyDescriptors
 from hysop.parameters.scalar_parameter import ScalarParameter, TensorParameter
 
 
-class IntegrateBase(object):
+class IntegrateBase(object, metaclass=ABCMeta):
     """
     Common implementation interface for field integration.
     """
 
-    __metaclass__ = ABCMeta
-
     @debug
     def __init__(self, field, variables,
                  name=None, pretty_name=None, cst=1,
diff --git a/hysop/operator/base/memory_reordering.py b/hysop/operator/base/memory_reordering.py
index 1d6c4c901..c07791e5c 100644
--- a/hysop/operator/base/memory_reordering.py
+++ b/hysop/operator/base/memory_reordering.py
@@ -8,13 +8,11 @@ from hysop.fields.continuous_field import ScalarField
 from hysop.core.memory.memory_request import MemoryRequest
 from hysop.topology.cartesian_descriptor import CartesianTopologyDescriptors
 
-class MemoryReorderingBase(object):
+class MemoryReorderingBase(object, metaclass=ABCMeta):
     """
     Common implementation interface for memory reordering operators.
     """
 
-    __metaclass__ = ABCMeta
-
     @debug
     def __init__(self, input_field, output_field, variables,
                     target_memory_order, name=None, pretty_name=None,
diff --git a/hysop/operator/base/redistribute_operator.py b/hysop/operator/base/redistribute_operator.py
index aa5ad03f3..0a7ebdf3a 100644
--- a/hysop/operator/base/redistribute_operator.py
+++ b/hysop/operator/base/redistribute_operator.py
@@ -8,23 +8,21 @@ from hysop.core.graph.computational_operator import ComputationalGraphOperator
 from hysop.topology.topology import Topology
 from hysop.fields.continuous_field import ScalarField
 
-class RedistributeOperatorBase(ComputationalGraphOperator):
+class RedistributeOperatorBase(ComputationalGraphOperator, metaclass=ABCMeta):
     """
     Abstract interface to redistribute operators.
     """
-    
-    __metaclass__ = ABCMeta
 
     @classmethod
     @not_implemented
     def can_redistribute(cls, source_topo, target_topo):
         """
-        Return true if this RedistributeOperatorBase can be applied 
+        Return true if this RedistributeOperatorBase can be applied
         to redistribute a variable from source_topo to target_topo,
         else return False.
         """
         pass
-    
+
     @classmethod
     def supported_backends(cls):
         """
@@ -41,7 +39,7 @@ class RedistributeOperatorBase(ComputationalGraphOperator):
             the variable to be distributed
         source_topo: :class:`~hysop.topology.topology.Topology`
             source mesh topology
-        target_topo: :class:`~hysop.topology.topology.Topology` 
+        target_topo: :class:`~hysop.topology.topology.Topology`
             target mesh topology
         name: str, optional
             name of this operator
@@ -51,7 +49,7 @@ class RedistributeOperatorBase(ComputationalGraphOperator):
         check_instance(variable, ScalarField)
         check_instance(source_topo, Topology)
         check_instance(target_topo, Topology)
-        
+
         input_fields  = {variable: source_topo}
         output_fields = {variable: target_topo}
 
@@ -69,41 +67,41 @@ class RedistributeOperatorBase(ComputationalGraphOperator):
 
         super(RedistributeOperatorBase, self).__init__(
                 name=name, pretty_name=pretty_name,
-                input_fields=input_fields, 
+                input_fields=input_fields,
                 output_fields=output_fields, **kwds)
-        
+
         self.variable    = variable
         self.dtype       = variable.dtype
 
         self.source_topo = source_topo
         self.target_topo = target_topo
-        
+
         # Set domain and mpi params
         self._set_domain_and_tasks()
 
     @debug
     def get_field_requirements(self):
         reqs = super(RedistributeOperatorBase,self).get_field_requirements()
-            
+
         for field in self.input_fields:
-            _, req = reqs.get_input_requirement(field) 
+            _, req = reqs.get_input_requirement(field)
             req.axes = None
-            req.memory_order = None 
-        
+            req.memory_order = None
+
         for field in self.output_fields:
-            _, req = reqs.get_output_requirement(field) 
+            _, req = reqs.get_output_requirement(field)
             req.axes = None
-            req.memory_order = None 
+            req.memory_order = None
 
         return reqs
-    
+
     @debug
     def get_node_requirements(self):
         from hysop.core.graph.node_requirements import OperatorRequirements
         reqs = super(RedistributeOperatorBase, self).get_node_requirements()
         reqs.enforce_unique_topology_shape=False
         return reqs
-        
+
     @debug
     def initialize(self, topgraph_method=None, **kwds):
         super(RedistributeOperatorBase,self).initialize(topgraph_method)
@@ -127,8 +125,8 @@ class RedistributeOperatorBase(ComputationalGraphOperator):
         return True
     def get_preserved_input_fields(self):
         return set(self.input_fields.keys())
-    
-    
+
+
     def available_methods(self):
         return {}
     def default_method(self):
diff --git a/hysop/operator/base/transpose_operator.py b/hysop/operator/base/transpose_operator.py
index bd91896df..e08f3fcd4 100644
--- a/hysop/operator/base/transpose_operator.py
+++ b/hysop/operator/base/transpose_operator.py
@@ -9,31 +9,29 @@ from hysop.fields.continuous_field import ScalarField
 from hysop.core.memory.memory_request import MemoryRequest
 from hysop.topology.cartesian_descriptor import CartesianTopologyDescriptors
 
-class TransposeOperatorBase(object):
+class TransposeOperatorBase(object, metaclass=ABCMeta):
     """
     Common implementation interface for transposition operators.
     """
 
-    __metaclass__ = ABCMeta
-
     @debug
     def __init__(self, input_field, output_field, variables, axes,
                    name=None, pretty_name=None, **kwds):
         """
         Initialize a transposition operator operating on CartesianTopologyDescriptors.
-        
+
         input_field: ScalarField
             Input continuous scalar field to be transposed, at least 2D.
         output_field: ScalarField
             Output continuous scalar field where the result is stored
             Transposed shape should match the input.
-            output_field can be the same as input_field resulting in 
+            output_field can be the same as input_field resulting in
             an inplace transposition.
         variables: dict
             Dictionary of fields as keys and CartesianTopologyDescriptors as values.
             Should contain input and output field.
         axes: tuple or list of ints
-            Permutation of axes in numpy notations 
+            Permutation of axes in numpy notations
             Axe dim-1 is the contiguous axe, axe 0 has the greatest stride in memory.
         kwds: dict
             Base class keyword arguments.
@@ -54,7 +52,7 @@ class TransposeOperatorBase(object):
 
         input_fields  = { input_field:  variables[input_field] }
         output_fields = { output_field: variables[output_field] }
-            
+
         saxes = ''.join([DirectionLabels[i] for i in axes]).lower()
         default_name = 'T{}_{}'.format(saxes, input_field.name)
         default_pname = u'T{}_{}'.format(saxes, input_field.pretty_name.decode('utf-8'))
@@ -67,7 +65,7 @@ class TransposeOperatorBase(object):
 
         super(TransposeOperatorBase, self).__init__(
                 input_fields=input_fields,
-                output_fields=output_fields, 
+                output_fields=output_fields,
                 name=name, pretty_name=pname,
                 **kwds)
 
@@ -76,24 +74,24 @@ class TransposeOperatorBase(object):
         self.nb_components = nb_components
         self.dim = dim
         self.axes = axes
-    
+
     @debug
     def get_node_requirements(self):
         from hysop.core.graph.node_requirements import OperatorRequirements
         reqs = super(TransposeOperatorBase, self).get_node_requirements()
         reqs.enforce_unique_transposition_state=False
         return reqs
-     
+
     def output_topology_state(self, output_field, input_topology_states):
         ostate = super(TransposeOperatorBase,self).output_topology_state(
-                        output_field=output_field, 
+                        output_field=output_field,
                         input_topology_states=input_topology_states)
         assert len(input_topology_states)==1
         istate = input_topology_states.values()[0]
         axes = self.axes
         ostate.axes = tuple( istate.axes[i] for i in axes )
         return ostate
-    
+
     @debug
     def discretize(self):
         if self.discretized:
@@ -102,17 +100,17 @@ class TransposeOperatorBase(object):
         self.din  = self.get_input_discrete_field(self.input_field)
         self.dout = self.get_output_discrete_field(self.output_field)
         self.is_inplace = (self.din.dfield is self.dout.dfield)
-    
+
     @debug
     def get_work_properties(self):
         requests  = super(TransposeOperatorBase,self).get_work_properties()
-        
+
         if self.is_inplace:
             request = MemoryRequest.empty_like(a=self.dout, nb_components=1)
             requests.push_mem_request('tmp', request)
-        
+
         return requests
-    
+
     @debug
     def setup(self, work):
         super(TransposeOperatorBase,self).setup(work)
@@ -120,16 +118,16 @@ class TransposeOperatorBase(object):
             raise ValueError('work is None.')
         if self.is_inplace:
             self.dtmp, = work.get_buffer(self, 'tmp')
-    
-            
+
+
     @staticmethod
     def get_preferred_axes(src_topo, dst_topo, candidate_axes):
         """
-        Return preferred transposition scheme (performance-wise) 
+        Return preferred transposition scheme (performance-wise)
         given source and destination topology and possible
         candidate transposition schemes.
-        
-        Candidate_axes is a dictionnary containing permutation 
+
+        Candidate_axes is a dictionnary containing permutation
         as keys (tuple of ints), and target transposition state
         (hysop.constants.TranspositionState) as values.
 
@@ -144,13 +142,13 @@ class TransposeOperatorBase(object):
         assert candidate_axes, 'candidate axes is None or empty.'
         dim = len(candidate_axes.keys()[0])
         tstates = TranspositionState[dim]
-        check_instance(candidate_axes, dict, keys=tuple, 
+        check_instance(candidate_axes, dict, keys=tuple,
                 values=(tstates, type(None)))
-        
+
         if tstates.default in candidate_axes.values():
             idx  = candidate_axes.values().index(tstates.default)
         else:
             idx = 0
-        
+
         axes = candidate_axes.keys()[idx]
         return axes
diff --git a/hysop/operator/directional/directional.py b/hysop/operator/directional/directional.py
index c3b7af685..6ef83b3a3 100644
--- a/hysop/operator/directional/directional.py
+++ b/hysop/operator/directional/directional.py
@@ -123,12 +123,11 @@ class DirectionalOperatorGeneratorI(object):
         return self.generate_direction(i, dt_coeff)
 
 
-class DirectionalOperatorGenerator(DirectionalOperatorGeneratorI):
+class DirectionalOperatorGenerator(DirectionalOperatorGeneratorI, metaclass=ABCMeta):
     """
     Simple ComputationalGraphNodeGenerator to generate an operator in
     multiple directions.
     """
-    __metaclass__ = ABCMeta
 
     @debug
     def __init__(self, operator, base_kwds,
@@ -286,14 +285,12 @@ class DirectionalOperatorGenerator(DirectionalOperatorGeneratorI):
         return {}
 
 
-class DirectionalOperatorFrontend(DirectionalOperatorGenerator):
+class DirectionalOperatorFrontend(DirectionalOperatorGenerator, metaclass=ABCMeta):
     """
     Frontend facility for directional operators that provide
     multiple implementations.
     """
 
-    __metaclass__ = ABCMeta
-
     @debug
     def __init__(self, implementation=None, base_kwds=None, **op_kwds):
         """
diff --git a/hysop/operator/hdf_io.py b/hysop/operator/hdf_io.py
index 033472524..a62bd7a8e 100755
--- a/hysop/operator/hdf_io.py
+++ b/hysop/operator/hdf_io.py
@@ -28,14 +28,12 @@ from hysop.core.memory.memory_request import MemoryRequest
 from hysop.topology.topology_descriptor import TopologyDescriptor
 
 
-class HDF_IO(ComputationalGraphOperator):
+class HDF_IO(ComputationalGraphOperator, metaclass=ABCMeta):
     """
     Abstract interface to read/write from/to hdf files, for
     hysop fields.
     """
 
-    __metaclass__ = ABCMeta
-
     @classmethod
     def supported_backends(cls):
         """
diff --git a/hysop/parameters/parameter.py b/hysop/parameters/parameter.py
index f09da60d8..33c42064a 100644
--- a/hysop/parameters/parameter.py
+++ b/hysop/parameters/parameter.py
@@ -10,12 +10,11 @@ from hysop.tools.handle import TaggedObject
 from hysop.tools.variable import Variable, VariableTag
 
 
-class Parameter(TaggedObject, VariableTag):
+class Parameter(TaggedObject, VariableTag, metaclass=ABCMeta):
     """
     A parameter is a value of a given type that may change value as simulation advances.
     Parameters are only available on the host backend.
     """
-    __metaclass__ = ABCMeta
 
     def __new__(cls, name, parameter_types,
                 initial_value=None, allow_None=False,
diff --git a/hysop/tools/enum.py b/hysop/tools/enum.py
index 6ca539c06..5dabce092 100644
--- a/hysop/tools/enum.py
+++ b/hysop/tools/enum.py
@@ -205,8 +205,7 @@ class EnumFactory(object):
                     '__repr__':__repr__}
         mcls = type(name+'MetaEnum', (EnumFactory.MetaEnum,), mcls_dic)
 
-        class Enum(base_cls):
-            __metaclass__=mcls
+        class Enum(base_cls, metaclass=mcls):
             def __init__(self, field=sorted(fields.keys())[0]):
                 assert isinstance(field, str) and len(field)>0
                 self._field = field
diff --git a/hysop/tools/handle.py b/hysop/tools/handle.py
index c51ea8c81..5a51e25e3 100644
--- a/hysop/tools/handle.py
+++ b/hysop/tools/handle.py
@@ -6,11 +6,9 @@ from hysop.tools.types import to_tuple, first_not_None
 from hysop.tools.sympy_utils import subscript
 from hysop.core.mpi import MPI
 
-class TaggedObjectView(object):
+class TaggedObjectView(object, metaclass=ABCMeta):
     """View on a TaggedObject, just forwards tag and id for views."""
 
-    __metaclass__ = ABCMeta
-
     @debug
     def __new__(cls, obj_view=None, **kwds):
         obj = super(TaggedObjectView, cls).__new__(cls, **kwds)
@@ -76,7 +74,7 @@ class TaggedObjectView(object):
         return self.full_tag
 
 
-class TaggedObject(object):
+class TaggedObject(object, metaclass=ABCMeta):
     """
     Generic class to count object instances and associate a tag to it.
     A tag is basically the id of the object instance formatted to a string.
@@ -84,8 +82,6 @@ class TaggedObject(object):
     object id (for logging or debug purposes).
     """
 
-    __metaclass__ = ABCMeta
-
     # Counter of instances to set a unique id for each object.
     __ids = {}
 
@@ -376,13 +372,11 @@ class RegisteredObject(TaggedObject):
      obj_initialized = property(__get_obj_initialized)
 
 
-class Handle(object):
+class Handle(object, metaclass=ABCMeta):
     """
     Generic class to encapsulate various objects ('handles').
     """
 
-    __metaclass__ = ABCMeta
-
     @classmethod
     @not_implemented
     def handle_cls(cls):
diff --git a/hysop/tools/interface.py b/hysop/tools/interface.py
index 3722820a0..d49604237 100644
--- a/hysop/tools/interface.py
+++ b/hysop/tools/interface.py
@@ -4,9 +4,8 @@ from hysop.tools.types import check_instance, first_not_None, to_tuple
 from hysop.tools.numpywrappers import npw
 
 
-class SymbolContainerI(object):
-    __metaclass__ = ABCMeta
-    
+class SymbolContainerI(object, metaclass=ABCMeta):
+
     def _get_symbol(self):
         """
         Return a Symbol that can be used to compute symbolic expressions
@@ -14,14 +13,13 @@ class SymbolContainerI(object):
         """
         assert hasattr(self, '_symbol'), 'Symbol has not been defined.'
         return self._symbol
-    
+
     symbol = property(_get_symbol)
     s = property(_get_symbol)
 
 
-class NamedObjectI(object):
-    __metaclass__ = ABCMeta
-    
+class NamedObjectI(object, metaclass=ABCMeta):
+
     def __new__(cls, name, pretty_name=None, latex_name=None, var_name=None, **kwds):
         """
         Create an abstract named object that contains a symbolic value.
@@ -33,21 +31,21 @@ class NamedObjectI(object):
         kwds: dict
             Keywords arguments for base class.
         """
-        
+
         obj = super(NamedObjectI, cls).__new__(cls, **kwds)
-        obj.rename(name=name, pretty_name=pretty_name, 
+        obj.rename(name=name, pretty_name=pretty_name,
                     latex_name=latex_name, var_name=var_name)
         return obj
-    
+
     def rename(self, name, pretty_name=None, latex_name=None, var_name=None):
         """Change the names of this object."""
         check_instance(name, str)
         check_instance(pretty_name, (str,unicode), allow_none=True)
         check_instance(latex_name, str, allow_none=True)
-        
+
         pretty_name = first_not_None(pretty_name, name)
         latex_name  = first_not_None(latex_name, name)
-        
+
         if isinstance(pretty_name, unicode):
             pretty_name = pretty_name.encode('utf-8')
         check_instance(pretty_name, str)
@@ -55,7 +53,7 @@ class NamedObjectI(object):
         self._name   = name
         self._pretty_name = pretty_name
         self._latex_name = latex_name
-        
+
     def _get_name(self):
         """Return the name of this field."""
         return self._name
@@ -65,10 +63,10 @@ class NamedObjectI(object):
     def _get_latex_name(self):
         """Return the latex name of this field."""
         return self._latex_name
-    
+
     def __str__(self):
         return self.long_description()
-    
+
     @abstractmethod
     def short_description(self):
         """Short description of this field as a string."""
@@ -89,18 +87,18 @@ class NamedScalarContainerI(NamedObjectI, SymbolContainerI):
     def ndim(self):
         """Number of dimensions of this this tensor."""
         return 0
-    
+
     def _get_var_name(self):
         """Return the variable name of this field."""
         return self._var_name
-    
-    def rename(self, name, pretty_name=None, 
+
+    def rename(self, name, pretty_name=None,
                 latex_name=None, var_name=None):
         """Change the names of this object."""
-        super(NamedScalarContainerI, self).rename(name=name, 
+        super(NamedScalarContainerI, self).rename(name=name,
                 pretty_name=pretty_name, latex_name=latex_name)
         self.check_and_set_varname(first_not_None(var_name, self._name))
-    
+
     def check_and_set_varname(self, var_name):
         check_instance(var_name, str, allow_none=True)
 
@@ -111,15 +109,15 @@ class NamedScalarContainerI(NamedObjectI, SymbolContainerI):
             if c in var_name:
                 raise RuntimeError(msg)
         self._var_name = var_name
-    
+
     def nd_iter(self):
         """Return an nd-indexed iterator of contained objects."""
         yield ((1,), self)
-    
+
     def __iter__(self):
         """Return an iterator on unique scalar objects."""
         return (self,).__iter__()
-    
+
     def __tuple__(self):
         """
         Fix hysop.tools/type.to_tuple for FieldContainers,
@@ -130,10 +128,10 @@ class NamedScalarContainerI(NamedObjectI, SymbolContainerI):
     def __contains__(self, obj):
         """Check if a scalar object is contained in self."""
         return (obj is self)
-    
+
     def __getitem__(self, slc):
         return self
-    
+
     var_name = property(_get_var_name)
 
 
@@ -143,14 +141,14 @@ class NamedTensorContainerI(NamedObjectI, SymbolContainerI):
         obj = super(NamedTensorContainerI, cls).__new__(cls, **kwds)
         obj._contained_objects = contained_objects
         return obj
-    
-    def rename(self, name, pretty_name=None, 
+
+    def rename(self, name, pretty_name=None,
                 latex_name=None, var_name=None):
         """Change the names of this object."""
         assert (var_name is None), 'Tensor do not have variable names.'
-        super(NamedTensorContainerI, self).rename(name=name, 
+        super(NamedTensorContainerI, self).rename(name=name,
                 pretty_name=pretty_name, latex_name=latex_name)
-    
+
     @property
     def size(self):
         """Full size of this container as if it was a 1D tensor."""
@@ -160,7 +158,7 @@ class NamedTensorContainerI(NamedObjectI, SymbolContainerI):
     def shape(self):
         """Shape of this tensor."""
         return self._contained_objects.shape
-    
+
     @property
     def ndim(self):
         """Number of dimensions of this this tensor."""
@@ -184,11 +182,11 @@ class NamedTensorContainerI(NamedObjectI, SymbolContainerI):
         """Return an nd-indexed iterator of contained objects."""
         for idx in npw.ndindex(*self._contained_objects.shape):
             yield (idx, self._contained_objects[idx])
-    
+
     def __iter__(self):
         """Return an iterator on unique scalar objects."""
         return self._contained_objects.ravel().__iter__()
-    
+
     def __tuple__(self):
         """
         Fix hysop.tools/type.to_tuple for FieldContainers,
@@ -199,7 +197,7 @@ class NamedTensorContainerI(NamedObjectI, SymbolContainerI):
     def __contains__(self, obj):
         """Check if a scalar object is contained in self."""
         return obj in self._contained_objects
-    
+
     @abstractmethod
     def __getitem__(self, slc):
         pass
diff --git a/hysop/tools/transposition_states.py b/hysop/tools/transposition_states.py
index 68db50f99..2222e6366 100644
--- a/hysop/tools/transposition_states.py
+++ b/hysop/tools/transposition_states.py
@@ -114,9 +114,8 @@ class TranspositionStateEnum(object):
     """TranspositionStateEnum base class."""
     pass
 
-class TranspositionState(object):
+class TranspositionState(object, metaclass=TranspositionStateType):
     """TranspositionState base class."""
-    __metaclass__ = TranspositionStateType
 
     __slots__ = ('_axes',)
 
diff --git a/hysop/tools/variable.py b/hysop/tools/variable.py
index 3773fca13..5e411dac8 100644
--- a/hysop/tools/variable.py
+++ b/hysop/tools/variable.py
@@ -6,9 +6,8 @@ from hysop.tools.decorators import debug
 
 Variable = EnumFactory.create('Variable', ['DISCRETE_FIELD', 'PARAMETER'])
 
-class VariableTag(object):
+class VariableTag(object, metaclass=ABCMeta):
     """Tag for HySoP variables."""
-    __metaclass__ = ABCMeta
 
     @debug
     def __new__(cls, variable_kind=None, **kwds):
@@ -16,7 +15,7 @@ class VariableTag(object):
         obj = super(VariableTag, cls).__new__(cls, **kwds)
         obj.__variable_kind = variable_kind
         return obj
-    
+
     @debug
     def __init__(self, variable_kind=None, **kwds):
         check_instance(variable_kind, Variable, allow_none=True)
diff --git a/hysop/topology/topology.py b/hysop/topology/topology.py
index 398ad19a1..9c972ea71 100644
--- a/hysop/topology/topology.py
+++ b/hysop/topology/topology.py
@@ -16,7 +16,7 @@ from hysop.core.arrays.array_backend import ArrayBackend
 from hysop.tools.types import check_instance, to_tuple, first_not_None
 from hysop.tools.parameters import MPIParams
 from hysop.tools.misc import Utils
-from hysop.tools.decorators import debug 
+from hysop.tools.decorators import debug
 from hysop.tools.numpywrappers import npw
 from hysop.tools.string_utils import prepend
 from hysop.tools.handle import RegisteredObject, TaggedObject, TaggedObjectView
@@ -28,20 +28,19 @@ class TopologyWarning(HysopWarning):
     """
     pass
 
-class TopologyState(TaggedObject):
+class TopologyState(TaggedObject, metaclass=ABCMeta):
     """
     Abstract base to define TopologyStates.
-        
+
     A TopologyState is a topology dependent state, and acts as a virtual state
-    that determines how we should perceive raw mesh data. 
-    
-    A TopologyState may for example include a transposition state like for 
+    that determines how we should perceive raw mesh data.
+
+    A TopologyState may for example include a transposition state like for
     CartesianTopology topologies.
     """
-    __metaclass__ = ABCMeta
 
     __slots__ = ('_is_read_only',)
-    
+
     @debug
     def  __init__(self, is_read_only, **kwds):
         """Initialize a topology state."""
@@ -54,13 +53,13 @@ class TopologyState(TaggedObject):
         return self._is_read_only
 
     is_read_only = property(_get_is_read_only)
-    
+
     @abstractmethod
     def match(self, other, invert=False):
         """Check if this topology state does match the other one."""
         res = (self._is_read_only == other._is_read_only)
         return (not res) if invert else res
-    
+
     @abstractmethod
     def __hash__(self):
         return hash(self._is_read_only)
@@ -69,7 +68,7 @@ class TopologyState(TaggedObject):
     def copy(self, is_read_only=None, **kwds):
         """Return a copy of self, some properties may be alterted in kwds."""
         pass
-   
+
     @abstractmethod
     def short_description(self):
         """Short description of this topology state."""
@@ -78,7 +77,7 @@ class TopologyState(TaggedObject):
     def long_description(self):
         """Long description of this topology state."""
         pass
-    
+
     def __eq__(self, other):
         return self.match(other)
     def __ne__(self, other):
@@ -88,31 +87,30 @@ class TopologyState(TaggedObject):
         return self.long_description()
 
 
-class TopologyView(TaggedObjectView):
+class TopologyView(TaggedObjectView, metaclass=ABCMeta):
     """
     Abstract base to define views on a Topology dependening on a TopologyState.
-    
+
     A TopologyView is a view on a Topology altered by a TopologyState.
     It is a lightweight object that keeps a reference on a Topology and a TopologyState.
 
-    A CartesianTopologyState may for example include a transposition state 
-    for CartesianTopology topologies, 
-    resulting in the automatic permutation of attributes when fetching the view attributes 
+    A CartesianTopologyState may for example include a transposition state
+    for CartesianTopology topologies,
+    resulting in the automatic permutation of attributes when fetching the view attributes
     (global_resolution and ghosts will be transposed).
     """
-    __metaclass__ = ABCMeta
 
     __slots__ = ('_mesh_view', '_domain_view', '_topology', '_topology_state')
-    
+
     @debug
     def __new__(cls, topology_state, topology=None, **kwds):
         """
         Create and initialize a TopologyView on given topology.
-        
+
         Parameters
         ----------
         topology_state: :class:`~hysop.topology.topology.TopologyState`
-            State that charaterizes the given view. 
+            State that charaterizes the given view.
         topology: :class:`~hysop.topology.topology.Topology`
             Original topology on which the view is.
         kwds: dict
@@ -123,17 +121,17 @@ class TopologyView(TaggedObjectView):
         topology: :class:`~hysop.topology.topology.Topology`
             Original topology on which the view is.
         topology_state: :class:`~hysop.topology.topology.TopologyState`
-            State that charaterizes the given view. 
-        
+            State that charaterizes the given view.
+
         domain : :class:`~hysop.domain.domain.Domain`
             The geometry on which the topology is defined.
         backend: :class:`~hysop.core.arrays.array_backend.ArrayBackend`
-            ArrayBackend of this topology. 
+            ArrayBackend of this topology.
         mpi_params: :class:`~hysop.tools.parameters.MPIParams`
             The parent MPI parameters of this topology.
-            /!\ Topologies may define a sub communicator 
+            /!\ Topologies may define a sub communicator
             (CartesianTopology topologies will define a MPI.Cartcomm for example).
-        
+
         parent: :class:`~hysop.core.mpi.IntraComm`
             Return the parent communicator used to build this topology.
         task_id: int
@@ -182,7 +180,7 @@ class TopologyView(TaggedObjectView):
         return self._topology._backend
     def _get_mpi_params(self):
         """The parent MPI parameters of this topology.
-            /!\ Topologies may define a sub communicator 
+            /!\ Topologies may define a sub communicator
                 CartesianTopology topologies will define a MPI.Cartcomm for example.
         """
         return self._topology._mpi_params
@@ -205,7 +203,7 @@ class TopologyView(TaggedObjectView):
     def _get_task_id(self):
         """Returns id of the task that owns this topology."""
         return self._topology._mpi_params.task_id
-    
+
     @abstractmethod
     def default_state(self):
         """Return the default topology state of this topology."""
@@ -215,12 +213,12 @@ class TopologyView(TaggedObjectView):
     def short_description(self, topo):
         """Short description of this topology."""
         pass
-    
+
     @abstractmethod
     def long_description(self, topo):
         """Long description of this topology."""
         pass
-    
+
     def match(self, other, invert=False):
         """Check if two TopologyViews are equivalent."""
         if not isinstance(other, TopologyView):
@@ -231,7 +229,7 @@ class TopologyView(TaggedObjectView):
             return not eq
         else:
             return eq
-    
+
     def __eq__(self, other):
         return self.match(other)
     def __ne__(self, other):
@@ -246,21 +244,21 @@ class TopologyView(TaggedObjectView):
 
     topology = property(_get_topology)
     topology_state = property(_get_topology_state)
-    
+
     backend    = property(_get_backend)
     mpi_params = property(_get_mpi_params)
     domain     = property(_get_domain)
     mesh       = property(_get_mesh)
 
     domain_dim = property(_get_domain_dim)
-    
+
     parent = property(_get_parent)
     task_id = property(_get_task_id)
 
-class Topology(RegisteredObject):
+class Topology(RegisteredObject, metaclass=ABCMeta):
     """
     Abstract base class for hysop Topologies.
-    
+
     In hysop, a topology is defined as the association of
     a mpi process distribution (mpi topology) and of a set of local meshes
     (one per process).
@@ -271,10 +269,8 @@ class Topology(RegisteredObject):
     You can also find examples of topologies instanciation in test_topology.py.
     """
 
-    __metaclass__ = ABCMeta
-    
     @debug
-    def __new__(cls, domain, mpi_params=None, backend=Backend.HOST, 
+    def __new__(cls, domain, mpi_params=None, backend=Backend.HOST,
             cl_env=None, allocator=None, queue=None, **kwds):
         """
         Creates or get an existing topology.
@@ -287,7 +283,7 @@ class Topology(RegisteredObject):
             MPI parameters (comm, task ...).
             If not specified, comm = domain.task_comm, task = domain.curent_task()
         backend: :class:`~hysop.constants.Backend` or `~hysop.core.arrays.ArrayBackend`, optional
-            Backend or backend kind for this topology. 
+            Backend or backend kind for this topology.
             By default a topology will use Backend.HOST.
         allocator: :class:`~hysop.core.memory.allocator.Allocator`, optional
             Allocated used on HOST backends instead of the default host memory pool allocator.
@@ -307,10 +303,10 @@ class Topology(RegisteredObject):
         Topologies can be uniquely identified by their id as they are hysop RegisteredObjects.
         See :class:`~hysop.tools.handle.RegisteredObject` for more information.
         """
-        
+
         # Create MPI parameters if necessary
         mpi_params = cls._create_mpi_params(mpi_params, domain, cl_env)
-       
+
         # Create backend if necessary
         backend = cls._create_backend(backend, mpi_params, allocator, cl_env, queue)
 
@@ -318,18 +314,18 @@ class Topology(RegisteredObject):
         check_instance(mpi_params, MPIParams)
         check_instance(backend, ArrayBackend)
         check_instance(domain, Domain)
-                
-        obj = super(Topology, cls).__new__(cls, backend=backend, 
+
+        obj = super(Topology, cls).__new__(cls, backend=backend,
                 mpi_params=mpi_params, domain=domain, tag_prefix='t', **kwds)
 
         if not obj.obj_initialized:
             obj._backend = backend
             obj._mpi_params = mpi_params
             obj._domain = domain
-            
+
         obj._domain.register_topology(obj)
         return obj
-            
+
     @classmethod
     def _create_mpi_params(cls, mpi_params, domain, cl_env):
         if (mpi_params is None):
@@ -339,7 +335,7 @@ class Topology(RegisteredObject):
             else:
                 mpi_params = cl_env.mpi_params
         task_id = mpi_params.task_id
-        
+
         msg = 'MPI task_id contained in mpi_params parameter is None.'
         assert (task_id is not None), msg
 
diff --git a/hysop/topology/topology_descriptor.py b/hysop/topology/topology_descriptor.py
index 81ac7454f..13334ce33 100644
--- a/hysop/topology/topology_descriptor.py
+++ b/hysop/topology/topology_descriptor.py
@@ -4,7 +4,7 @@ from hysop.tools.types import check_instance
 from hysop.topology.topology import Topology, TopologyView
 from hysop.mesh.mesh import Mesh
 
-class TopologyDescriptor(object):
+class TopologyDescriptor(object, metaclass=ABCMeta):
     """
     Describes how a topology should be built.
 
@@ -12,14 +12,13 @@ class TopologyDescriptor(object):
     operator graph building and are replaced by a single unique
     topology upon initialization.
     """
-    __metaclass__ = ABCMeta
 
     __slots__ = ('_mpi_params', '_domain', '_backend', '_extra_kwds')
 
     def __init__(self, mpi_params, domain, backend, **kwds):
         """
         Initialize a TopologyDescriptor.
-        
+
         Notes
         -----
         kwds allows for backend specific variables.
@@ -49,13 +48,13 @@ class TopologyDescriptor(object):
     def _get_extra_kwds(self):
         """Get extra keyword arguments."""
         return dict(self._extra_kwds)
-        
+
     mpi_params  = property(_get_mpi_params)
     domain      = property(_get_domain)
     backend     = property(_get_backend)
     extra_kwds  = property(_get_extra_kwds)
     dim         = property(_get_dim)
-    
+
     @staticmethod
     def build_descriptor(backend, operator, field, handle, **kwds):
         """
@@ -79,7 +78,7 @@ class TopologyDescriptor(object):
             # handle is already a TopologyDescriptor, so we return it.
             return handle
         elif isinstance(handle, CartesianTopologyDescriptors):
-            return CartesianTopologyDescriptor.build_descriptor(backend, operator, 
+            return CartesianTopologyDescriptor.build_descriptor(backend, operator,
                     field, handle, **kwds)
         elif (handle is None):
             # this topology will be determined later
@@ -88,7 +87,7 @@ class TopologyDescriptor(object):
             msg='Unknown handle of class {} to build a TopologyDescriptor.'
             msg=msg.format(handle.__class__)
             raise TypeError(msg)
-    
+
     def choose_or_create_topology(self, known_topologies, **kwds):
         """
         Returns a topology that is either taken from known_topologies, a set
@@ -101,7 +100,7 @@ class TopologyDescriptor(object):
         if (topo is None):
             topo = self.create_topology(**kwds)
         return topo
-    
+
     @abstractmethod
     def choose_topology(self, known_topologies, **kwds):
         """
@@ -140,8 +139,8 @@ class TopologyDescriptor(object):
 
     @abstractmethod
     def __hash__(self):
-        h  = id(self.domain) 
-        h ^= hash(self.mpi_params) 
+        h  = id(self.domain)
+        h ^= hash(self.mpi_params)
         h ^= hash(self.backend)
         h ^= hash(self._extra_kwds)
         return h
-- 
GitLab