Search code examples
pythonabcabstract-base-class

Python Abstract Base Classes: Why doesn't abc prevent instantiation?


As far as I understood, the Python module abc should prevent instantiation of classes which have not all @abstractmethod marked methods of the base class implemented (provided that the base class has __metaclass__ = ABCMeta set)

However, this seems not to work with the following code:

Abstract base class:

""" Contains payment processors for executing payments """

from abc import ABCMeta, abstractmethod

class AbstractPaymentProcessor:
    """ Abstract class for executing faucet Payments
    Implement this at your own. Possible implementations include
    online wallets and RPC calls to running dogecoin wallets """

    __metaclass__ = ABCMeta

    @abstractmethod
    def execute_payment(self, destination_address, amount):
        """ Execute a payment to one receiving single address

        return the transaction id or None """
        pass

    @abstractmethod
    def execute_multi_payment(self, destination_addresses, amounts):
        """ Execute a payment to multiple receiving addresses

        return the transaction id or None """
        pass

    @abstractmethod
    def get_transaction_status(self):
        """ Get the status of the transaction

        Indicate if transaction is already confirmed. Return
         - True if confirmed
         - False if unconfirmed
         - None if transaction doesn't exist (or raise exception?)"""
        pass

    @abstractmethod
    def get_available_balance(self):
        """ Get the available balance
        i.e. how much "cash" is in the faucet """
        pass

subclass missing a method:

""" Contains a logging payment processor """

import logging
import random

from AbstractPaymentProcessor import AbstractPaymentProcessor

class DummyLoggingPaymentProcessor (AbstractPaymentProcessor):
    """ Payment processor that does nothing, just logs """

    def __new__(self):
        self._logger = logging.getLogger(__name__)
        self._logger.setLevel(logging.INFO)

    def execute_payment(self, destination_address, amount):
        """ Execute a payment to one receiving single address

        return the transaction id or None """
        raise NotImplementedError("Not implemented yet")

    def execute_multi_payment(self, destination_addresses, amounts):
        """ Execute a payment to multiple receiving addresses

        return the transaction id or None """
        raise NotImplementedError("Not implemented yet")

    def get_transaction_status(self):
        """ Get the status of the transaction

        Indicate if transaction is already confirmed. Return
         - True if confirmed
         - False if unconfirmed
         - None if transaction doesn't exist """
        raise NotImplementedError("Not implemented yet")


if __name__ == '__main__':
    # can instanciate, although get_available_balance is not defined. Why? abc should prevent this!?
    c = DummyLoggingPaymentProcessor()
    c.get_available_balance()

The subclass can be instantiated in the (quite crude) test code. Why is that so?

I'm using Python 2.7.


Solution

  • You are overriding __new__; it is this method (on object.__new__) that prevents the instantiation.

    You are not creating a immutable type here or otherwise are altering new object creation, so use __init__ instead:

    def __init__(self):
        self._logger = logging.getLogger(__name__)
        self._logger.setLevel(logging.INFO)
    

    You were using __new__ wrong in any case; the first argument passed in is the class, not an instance as no instance has been created at that point. By overriding __new__ and not calling the original, you a) are not creating an instance and b) not triggering the code that prevents the instance being created in the first place.

    With __init__ instead of __new__ instantiation raises an exception as expected:

    >>> class DummyLoggingPaymentProcessor (AbstractPaymentProcessor):
    ...     """ Payment processor that does nothing, just logs """
    ...     def __init__(self):
    ...         self._logger = logging.getLogger(__name__)
    ...         self._logger.setLevel(logging.INFO)
    ...     def execute_payment(self, destination_address, amount):
    ...         """ Execute a payment to one receiving single address
    ... 
    ...         return the transaction id or None """
    ...         raise NotImplementedError("Not implemented yet")
    ...     def execute_multi_payment(self, destination_addresses, amounts):
    ...         """ Execute a payment to multiple receiving addresses
    ... 
    ...         return the transaction id or None """
    ...         raise NotImplementedError("Not implemented yet")
    ...     def get_transaction_status(self):
    ...         """ Get the status of the transaction
    ... 
    ...         Indicate if transaction is already confirmed. Return
    ...          - True if confirmed
    ...          - False if unconfirmed
    ...          - None if transaction doesn't exist """
    ...         raise NotImplementedError("Not implemented yet")
    ... 
    >>> c = DummyLoggingPaymentProcessor()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: Can't instantiate abstract class DummyLoggingPaymentProcessor with abstract methods get_available_balance