Search code examples
python-3.xabc

Difference between from collections import Container and from collections.abc import Container


We can import Container in two ways:

  1. from collections import Container
  2. from collections.abc import Container

help function for both Container returns the same documentation.

help(collections.Container):

Help on class Container in module collections.abc:

class Container(builtins.object)
 |  Methods defined here:
 |  
 |  __contains__(self, x)
 |  
 |  ----------------------------------------------------------------------
 |  Class methods defined here:
 |  
 |  __subclasshook__(C) from abc.ABCMeta
 |      Abstract classes can override this to customize issubclass().
 |      
 |      This is invoked early on by abc.ABCMeta.__subclasscheck__().
 |      It should return True, False or NotImplemented.  If it returns
 |      NotImplemented, the normal algorithm is used.  Otherwise, it
 |      overrides the normal algorithm (and the outcome is cached).
 |  
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |  
 |  __abstractmethods__ = frozenset({'__contains__'})

help(collections.abc.Container):

Help on class Container in module collections.abc:

class Container(builtins.object)
 |  Methods defined here:
 |  
 |  __contains__(self, x)
 |  
 |  ----------------------------------------------------------------------
 |  Class methods defined here:
 |  
 |  __subclasshook__(C) from abc.ABCMeta
 |      Abstract classes can override this to customize issubclass().
 |      
 |      This is invoked early on by abc.ABCMeta.__subclasscheck__().
 |      It should return True, False or NotImplemented.  If it returns
 |      NotImplemented, the normal algorithm is used.  Otherwise, it
 |      overrides the normal algorithm (and the outcome is cached).
 |  
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |  
 |  __abstractmethods__ = frozenset({'__contains__'})

What is the difference between these two imports? Why are we allowed to do both?

Update

Got deprecation warning while importing Container from collections (Python 3.7.3).

From Python 3.8 it cannot be imported directly from collections.

>>> from collections import Container

main:1: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working


Solution

  • From the Python 3 documentation for the collections module:

    Changed in version 3.3: Moved Collections Abstract Base Classes to the collections.abc module. For backwards compatibility, they continue to be visible in this module through Python 3.7. Subsequently, they will be removed entirely.

    These "Collections Abstract Base Classes" currently include AsyncGenerator, AsyncIterable, AsyncIterator, Awaitable, Bytestring, Callable, Collection, Container, Coroutine, Generator, Hashable, ItemsView, Iterable, Iterator, KeysView, Mapping, MappingView, MutableMapping, MutableSequence, MutableSet, Reversible, Sequence, Set, Sized, ValuesView.

    In Python 3.8 importing them from collections will stop working. In Python 3.3 to 3.7, they can be imported from collections or from collections.abc (it gives the exact same classes). In Python 3.7, importing them from collections prints a deprecation warning, since Python 3.8 is getting near.

    In Python 2 they can only be imported from 'collections', not from 'collections.abc'.

    One simple way to deal with this is a try/except block:

    try:  # works in Python >= 3.3
        from collections.abc import Sequence
    except ImportError:  # Python 2, Python <= 3.2
        from collections import Sequence
    

    Another commonly used workaround is to conditionally import from collections or collections.abc depending on the Python version being used.

    For instance, have a PY2 boolean and do:

    if PY2:
        from collections import Sequence
    else:
        from collections.abc import Sequence
    

    This boolean is usually obtained either using six:

    from six import PY2
    

    or using sys.version_info:

    import sys
    PY2 = int(sys.version_info[0]) == 2
    

    If we anticipate that Python 4 is likely to work like Python 3.3+ in this respect, special-casing Python 2 seems more future-proof than special-casing Python 3, which could be done as follows:

    if PY3:
        from collections.abc import Sequence
    else:
        from collections import Sequence
    

    where the PY3 boolean can be obtained either using six:

    from six import PY3
    

    or using sys.version_info:

    import sys
    PY3 = int(sys.version_info[0]) == 3
    

    The try/except approach above seems even more robust though (e.g. it works with Python 3.2 with no extra effort).