Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using abc.ABCMeta in a way it is compatible both with Python 2.7 and Python 3.5

I'd like to create a class which has abc.ABCMeta as a metaclass and is compatible both with Python 2.7 and Python 3.5. Until now, I only succeeded doing this either on 2.7 or on 3.5 - but never on both versions simultaneously. Could someone give me a hand?

Python 2.7:

import abc class SomeAbstractClass(object):     __metaclass__ = abc.ABCMeta     @abc.abstractmethod     def do_something(self):         pass 

Python 3.5:

import abc class SomeAbstractClass(metaclass=abc.ABCMeta):     @abc.abstractmethod     def do_something(self):         pass 

Testing

If we run the following test using the suitable version of the Python interpreter (Python 2.7 -> Example 1, Python 3.5 -> Example 2), it succeeds in both scenarios:

import unittest class SomeAbstractClassTestCase(unittest.TestCase):     def test_do_something_raises_exception(self):         with self.assertRaises(TypeError) as error:             processor = SomeAbstractClass()         msg = str(error.exception)         expected_msg = "Can't instantiate abstract class SomeAbstractClass with abstract methods do_something"         self.assertEqual(msg, expected_msg) 

Problem

While running the test using Python 3.5, the expected behavior doesn't happen (TypeError is not raised while instantiating SomeAbstractClass):

====================================================================== FAIL: test_do_something_raises_exception (__main__.SomeAbstractClassTestCase) ---------------------------------------------------------------------- Traceback (most recent call last):   File "/home/tati/sample_abc.py", line 22, in test_do_something_raises_exception     processor = SomeAbstractClass() AssertionError: TypeError not raised  ---------------------------------------------------------------------- 

Whereas running the test using Python 2.7 raises a SyntaxError:

 Python 2.7 incompatible  Raises exception:   File "/home/tati/sample_abc.py", line 24     class SomeAbstractClass(metaclass=abc.ABCMeta):                                      ^  SyntaxError: invalid syntax 
like image 898
Tatiana Al-Chueyr Avatar asked Feb 27 '16 18:02

Tatiana Al-Chueyr


People also ask

What is the difference between ABC and ABCMeta Python?

ABCMeta . i.e abc. ABC implicitly defines the metaclass for us. The only difference is that in the former case you need a simple inheritance and in the latter you need to specify the metaclass.

What is ABC Meta in Python?

ABCMeta metaclass provides a method called register method that can be invoked by its instance. By using this register method, any abstract base class can become an ancestor of any arbitrary concrete class.


2 Answers

Using abc.ABCMeta in a way it is compatible both with Python 2.7 and Python 3.5

If we were only using Python 3 (this is new in 3.4) we could do:

from abc import ABC 

and inherit from ABC instead of object. That is:

class SomeAbstractClass(ABC):     ...etc 

You still don't need an extra dependence (the six module) - you can use the metaclass to create a parent (this is essentially what the six module does in with_metaclass):

import abc  # compatible with Python 2 *and* 3: ABC = abc.ABCMeta('ABC', (object,), {'__slots__': ()})   class SomeAbstractClass(ABC):      @abc.abstractmethod     def do_something(self):         pass 

Or you could just do it in-place (but this is more messy, and doesn't contribute as much to reuse):

# use ABCMeta compatible with Python 2 *and* 3  class SomeAbstractClass(abc.ABCMeta('ABC', (object,), {'__slots__': ()})):      @abc.abstractmethod     def do_something(self):         pass 

Note that the signature looks a little messier than six.with_metaclass but it is substantially the same semantics, without the extra dependence.

Either solution

and now, when we try to instantiate without implementing the abstraction, we get precisely what we expect:

>>> SomeAbstractClass() Traceback (most recent call last):   File "<pyshell#31>", line 1, in <module>     SomeAbstractClass() TypeError: Can't instantiate abstract class SomeAbstractClass with abstract methods do_something 

Note on __slots__ = ()

We just added empty __slots__ to the ABC convenience class in Python 3's standard library, and my answer is updated to include it.

Not having __dict__ and __weakref__ available in the ABC parent allows users to deny their creation for child classes and save memory - there are no downsides, unless you were using __slots__ in child classes already and relying on implicit __dict__ or __weakref__ creation from the ABC parent.

The fast fix would be to declare __dict__ or __weakref__ in your child class as appropriate. Better (for __dict__) might be to declare all your members explicitly.

like image 32
Russia Must Remove Putin Avatar answered Oct 05 '22 22:10

Russia Must Remove Putin


You could use six.add_metaclass or six.with_metaclass:

import abc, six  @six.add_metaclass(abc.ABCMeta) class SomeAbstractClass():     @abc.abstractmethod     def do_something(self):         pass 

six is a Python 2 and 3 compatibility library. You can install it by running pip install six or by downloading the latest version of six.py to your project directory.

For those of you who prefer future over six, the relevant function is future.utils.with_metaclass.

like image 131
vaultah Avatar answered Oct 05 '22 23:10

vaultah