Skip to content
scipython.com

P4.6.1Modifying the BankAccount class

(a) Modify the base BankAccount class (Section 4.6.2 of the book) to verify that the account number passed to its __init__ constructor conforms to the Luhn algorithm described in Exercise P2.5.3.

(b) Modify the CurrentAccount class to implement a free overdraft. The limit should be set in the __init__ constructor; withdrawals should be allowed to within the limit.

Show solution

(a) The definition of BankAccount in the code that follows uses the Luhn checksum method to validate the account number.

The account number may be provided as an integer or as a string (possibly with spaces or hyphens delimiting groups of numbers):

>>> from bank_account2 import BankAccount
>>> BankAccount('John Brown', 7298-904')
<bank_account2.BankAccount at 0x103fc1350>

>>> BankAccount('John Brown', 7298905)
...
ValueError: Invalid account number

(b) An interest-free overdraft is implemented in the CurrentAccount class below as well; a change has been made to the base class's BankAccount method to output negative balances cleanly.

>>> from bank_account2 import CurrentAccount

>>> account = CurrentAccount('John Brown', '729-8904', 0, 500)
>>> account.deposit(400)

>>> account.withdraw(350)

>>> account.check_balance()
The balance of account number 729-8904 is $50.00

>>> account.withdraw(200)

>>> account.check_balance()
The balance of account number 729-8904 is -$150.00

>>> account.withdraw(600)
$600.00 exceeds the single transaction limit of $500.00

>>> account.withdraw(500)

>>> account.check_balance()
The balance of account number 729-8904 is -$650.00

>>> account.withdraw(500)
Insufficient funds

The modified code is:

class BankAccount:
    """A abstract base class representing a bank account."""

    currency = "$"

    def __init__(self, customer, account_number, balance=0):
        """
        Initialize the BankAccount class with a customer, account number
        and opening balance (which defaults to 0.)

        """

        self.customer = customer
        self.set_account_number(account_number)
        self.balance = balance

    def set_account_number(self, account_number):
        if not self.verify_account_number(account_number):
            raise ValueError("Invalid account number")

        self.account_number = account_number

    def verify_account_number(self, account_number):
        """Verify the account number, returning True if it is valid."""

        ccl = [int(d) for d in str(account_number) if d not in ("-", " ")]
        for i in range(len(ccl) - 2, 0, -2):
            ccl[i] *= 2
            if ccl[i] > 9:
                ccl[i] = 1 + (ccl[i] - 10)
        checksum = sum(ccl) % 10
        return not checksum

    def deposit(self, amount):
        """Deposit amount into the bank account."""
        if amount > 0:
            self.balance += amount
        else:
            print("Invalid deposit amount:", amount)

    def withdraw(self, amount):
        """
        Withdraw amount from the bank account, ensuring there are sufficient
        funds.

        """
        if amount > 0:
            if amount > self.balance:
                print("Insufficient funds")
            else:
                self.balance -= amount
        else:
            print("Invalid withdrawal amount:", amount)

    def check_balance(self):
        """Print a statement of the account balance."""

        s_sign = "-" if self.balance < 0 else ""
        print(
            f"The balance of account number {self.account_number} is"
            f" {s_sign}{self.currency}{abs(self.balance):.2f}"
        )


class CurrentAccount(BankAccount):
    """A class representing a current (checking) account."""

    def __init__(
        self,
        customer,
        account_number,
        annual_fee,
        transaction_limit,
        balance=0,
        overdraft_limit=1000,
    ):
        """Initialize the current account."""

        self.annual_fee = annual_fee
        self.transaction_limit = transaction_limit
        self.overdraft_limit = overdraft_limit
        super().__init__(customer, account_number, balance)

    def withdraw(self, amount):
        """
        Withdraw amount if sufficient funds exist in the account and amount
        is less than the single transaction limit.

        """
        if amount <= 0:
            print("Invalid withdrawal amount:", amount)
            return

        if amount > self.balance + self.overdraft_limit:
            print("Insufficient funds")
            return

        if amount > self.transaction_limit:
            print(
                f"{self.currency}{amount:.2f} exceeds the single transaction"
                f" limit of {self.currency}{self.transaction_limit:.2f}"
            )
            return

        self.balance -= amount

    def apply_annual_fee(self):
        """Deduct the annual fee from the account balance."""

        self.balance = max(0.0, self.balance - self.annual_fee)