diff --git a/hysop/backend/device/autotunable_kernel.py b/hysop/backend/device/autotunable_kernel.py
index 170f3348ec1006dbc3ccc0f649018a7ce0ae9ba0..0bec40f408d6309089a942fc41f4f91b8037ac1d 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 5b29dd56fdfb8ec2a6d08407e137bf36f98c99b5..3f2e68fafd318f57828c3f10eeaac70542b675df 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 066f9382e1b54b44bd229aa16a489092c311a623..df977b761da2fcbe0fe802e3ea2129baf676ae4f 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 096eec3849a77312ff17b68ff3134b36c969742a..63df8bdde35f4b5b43af1ddf31cc535a704fd871 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 7bb7792d9d61e1fe51110e9cf8910080b787b9e3..f4a6e9096f3a119a2c86ffea11a1420332bb00a9 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 ee4483e49fc367fe0adc538371c6efdff2a211a0..b8ed3d302944b7072493ecf13160c523a157d41b 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 334ab9e34cbbad72498c1a64e739fda4b42f81ba..6db66240464c2c952b580e87fe46ddae7904743a 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 d9e74c926d3b17934518065e1063164ecec49768..44682869e544b2956bc2fea40e91470877ce1fc2 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 44d8f8b0c7b6ee5fc8232ac7042cdacb156d9e00..e44b2a0f475d162cfda743d8f65f1fd47e880d08 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 0d203fc6377340fc907a8610d471512945852782..b96823f279fec274279c15a8bc14f062beb63301 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 b88796c7b012d79a6338f8bc5ae68514950a3ea8..5359257012ec78f11fe07bf35150ebde20c662c4 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 21fa5ab05a1aaad52ea4acc2393367c6721fc7c8..7c80319116777024ad6c075c76e3cf07d1600498 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 227203cac644e4e2c4fdb7b00ddf4f353d499bd6..5510a00d87c11b46c202a95d3f2175516cffe451 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 a7a1d68c56d39f49c8771368c62fca1373ddf642..fbf4d5af2aa60ad849dbd159910520c0540c66c3 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 dc4e9816f00d3f3bd7b3fcd4d711b49e90863ed2..f43dccf690e0dff5a384e3933c65e4169fb02497 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 bf818f257ef66bf49f683a6097b2b4bd40cf0045..8c86415aba72e881abbce545799ea512ea369a9c 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 27a8f7b48266d1e6fcbb87381cb659168a05eb93..a837463bd8ea8ea0cb382df5025d0e809977934a 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 82b3812be7ccf85c92bc05de1aedd809892ffe7f..07fb46176d8ab34e68b3094031ec11440cbf3700 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 c8d3f7a24eee0e172c5ff62b4452295913183837..08cbe0b85e27550b0beecc47cf83347bd436eeb0 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 7f5226ffbcaa550fca52de6834f015273e8dec05..d8a5eba08c01846028b2aaf699380e476b969cd2 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 160db9b45ac5d327f8b7e71f5ddf446133365357..bd394fa0682682c62e500fc18264a673b7463492 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 b71d3bc4c53634d244aaf5061ad3998f726b5266..afccabccc2afb225a3e4276396e32034db064ad2 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 ae2e358bc73b519e02a706555795024111979d3c..75a4d5ec994459a1554e4ce813c985b9566fd823 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 070e8902f4086ff92242fe1c06701c5d79a1eae7..402f61facb655b41d06a826934980ff7ea30e4da 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 098af43f77836009a39619629ac58d8a9ce672e7..4ef44783fa384bd7378a45df6cb179a2efc23677 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 b3dcd7c9eb0e0abcd1fa0d6b676fef84d2933e7a..7b0ffa361b4a5b3aa7ddbbf9a15d645026c50fd6 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 277c95371bb5a3700b7ac528e3630511a81b7e32..a7f696db40e31c50893c3c39e84e1df7d0b39352 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 1490a15a88cf54eb1ce05e4d2a67137514ef9370..aafa62d9994c11db36448b5fa1a490de7221ecd9 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 75087be1275d7fc7b84bc90465c5e62ce0ce697b..014c3e6babe23b09c238907344527735df1a9de4 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 4f30aa6003a63553b00f5b6a5cf9e27c54d72401..2b7993d7311299e973e5ae17ac1c0be83cd64441 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 494fb6550b4082ff1d1d9317362b08b7dfb63c7a..c54495669d0252bb8109006c7c4487c89d5fbd9b 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 b547e65382a45d6e81a66643352508259ae367b2..65e91de33adf39e47be1cd4fde59f1117aaebddd 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 408432613dc84f0854d736948b0dd372b430cd02..bba8012dfdb6db788b3c1733246c850f69acef3e 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 834416f58c9259745e2977787b7722f9a57a59f8..5b860814b449b6bb6a10b5ddba618af00e0a2c68 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 1305461cad775707c2b004c18d5b7ad629306b41..f2c401b0548c3e6451d54cf6a7f76ba12b1d354f 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 7cfb017985d830c467651d9b25a5a3bb7213a419..59d9cb5adcd6c9620d6755c54d0ad6ddaecd459e 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 bfbda81770bf998931a54c9d320c2cfd6534721e..2a95335576cffc9f5c7e04b32bf80d9371848dc7 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 1d6c4c9019a52a33181979a02bce56115b58830c..c07791e5cf586468e31f5b17660f9a94e4608233 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 aa5ad03f312d515ae93c37985494e904ade26269..0a7ebdf3a7aa5f692b9c6a4eaf2ea9d0ff31bee7 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 bd91896df16c2bcbb25e089450702082132f64a3..e08f3fcd4707c9e2562bc06151380f5660af9ad7 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 c3b7af6853dfd11fdcb7b3070e8459a15c88395b..6ef83b3a38c52509832534266acfc2ff75f32bba 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 0334725246d1f8cb1b7d16956bed31b2b6b34639..a62bd7a8e38401a7f53e2cef8573f62ff11a8977 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 f09da60d8fd6dba4678cd57cf60cee96ed3925b0..33c42064a1913ee15a10da4d32e1c65fa5c3db1a 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 6ca539c068034cf7f4ae55b306b3ee8baef347b6..5dabce0925a19249c9f8bb50d657f4de9f6a216d 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 c51ea8c81d901657e91de5ce17b9237edd906078..5a51e25e39c3497e605fa6c43f7d04a7d755ef01 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 3722820a076d08c4f7c5e21308d84799474fcba5..d49604237fbe4ea0ecf133829678ae600af2eb17 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 68db50f99cca745ca1b0fcb022923a4886313fe0..2222e6366ae538310a841e691e312e5b0c23dc7b 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 3773fca139a17b016256dfad29f3aa6aea2149a1..5e411dac8fe436d30395d0aea540e80cba78e0b2 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 398ad19a199203e526b60b8b2e6013efb7785217..9c972ea714ee5917d89a5293671aa0abc04e879f 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 81ac7454fd27d6deb451e0e1ee69f0ecbcacc677..13334ce338cca41817c8534601141f63c4d0afdb 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