patternpythonModerate
Basic Python OO bank account
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:
BankAccount.py
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
Example usage being:
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.
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.