HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonModerate

Basic Python OO bank account

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
bankaccountpythonbasic

Problem

I am learning Python and in particular learning about classes. Since I'm self teaching, I'd appreciate any feedback and constructive criticism.

I have a couple specific questions:

  • In Python you can have multiple functions and classes in a single file (module), but is it considered bad practice to do so?



  • Python doesn't have private, so is there a point to having accessor methods like I do below?



  • My IDE suggest I put a comment beneath each function signature. Is it still good to do this when the functions are short and simple, like withdraw(x) in a bank account?



BankAccount.py

# bank account class
class BankAccount:
    def __init__(self, name, balance=0.00):
        self.name = name
        self.balance = balance

    def deposit(self, amount):
        """make a deposit"""
        self.balance += amount

    def withdraw(self, amount):
        """make a withdraw"""
        if amount > self.balance:
            raise ValueError("insufficient funds")
        self.balance -= amount

    def get_balance(self): #are accessor methods needed in Python?
        """check the balance"""
        return self.balance

def main():
    customer1 = BankAccount('Alex')
    print(customer1.get_balance())
    customer1.deposit(100)
    customer1.withdraw(30)
    print(customer1.get_balance())

    customer2 = BankAccount('Sam', 200)
    print(customer2.get_balance())

if __name__ == "__main__":
    main()

Solution

Python doesn't have private anything but we still have a need to separate inners of a class from the public interface.

By convention, attributes or methods whose name starts with a single underscore are considered internals of the class and, thus, not part of the public API. There is no mechanism in place to restrict their access, so user that know what they are doing can still use them; but if anything goes wrong, it will most likely be their fault, not yours.

Also, in the context of versionned libraries, internals are not subject to backward compatibility and can change without notice on a new release.

But instead of methods as getter or setters, we prefer properties that allows the same kind of flexibility (read-only vs. read-write) with the simplicity of attributes access.

Appart from that, I would add either __str__ or __repr__ (or both) as methods in your class to simplify printing accounts:

class BankAccount:
    def __init__(self, name, balance=0.00):
        self.name = name
        self._balance = balance

    def deposit(self, amount):
        """make a deposit"""
        self._balance += amount

    def withdraw(self, amount):
        """make a withdraw"""
        if amount > self._balance:
            raise ValueError("insufficient funds")
        self._balance -= amount

    @property
    def balance(self):
        """check the balance"""
        return self._balance

    def __repr__(self):
        return '{0.__class__.__name__}(name={0.name}, balance={0.balance})'.format(self)

    def __str__(self):
        return 'Bank account of {}, current balance: {}'.format(self.name, self.balance)


Example usage being:

customer1 = BankAccount('Alex')
print(repr(customer1))
customer1.deposit(100)
customer1.withdraw(30)
print(customer1)
customer2 = BankAccount('Sam', 200)
print(customer2.balance)


As regard to the "one class per file" rule, it may make sense in languages where a file does not bear much meaning.

But in python a file is a module. It is perfectly fine and expected to put related entities in them as they define a namespace.

Beside, where would you put simple functions and constants using a "one class per file" approach? As class attribute of some sort of "namespace" class? Yuck! Module already serve this purpose, don't reinvent the wheel.

Code Snippets

class BankAccount:
    def __init__(self, name, balance=0.00):
        self.name = name
        self._balance = balance

    def deposit(self, amount):
        """make a deposit"""
        self._balance += amount

    def withdraw(self, amount):
        """make a withdraw"""
        if amount > self._balance:
            raise ValueError("insufficient funds")
        self._balance -= amount

    @property
    def balance(self):
        """check the balance"""
        return self._balance

    def __repr__(self):
        return '{0.__class__.__name__}(name={0.name}, balance={0.balance})'.format(self)

    def __str__(self):
        return 'Bank account of {}, current balance: {}'.format(self.name, self.balance)
customer1 = BankAccount('Alex')
print(repr(customer1))
customer1.deposit(100)
customer1.withdraw(30)
print(customer1)
customer2 = BankAccount('Sam', 200)
print(customer2.balance)

Context

StackExchange Code Review Q#160840, answer score: 16

Revisions (0)

No revisions yet.