Bank Account Sub classes OOP - python-3.x

B)The BankAccount class should then have two child classes, named SavingsAccount and NonTaxFilerAccount. The SavingsAccount class should have a ZakatDeduction( )function which deducts an amount of 2.5% of the current account balancewhen called. The NonTaxFilerAccount class should have the withdraw function from the parent class overwritten. Where a 2% withholding tax is deducted from the account every timea withdrawal is made.
i did the first part but not getting the second one it keeps giving me attribute error
class BankAccount:
def __init__(self, init_bal):
"""Creates an account with the given balance."""
self.init_bal = init_bal
self.account = init_bal
def deposit(self, amount):
"""Deposits the amount into the account."""
self.amount = amount
self.account += amount
def withdraw(self, amount):
self.account -= amount
def balance(self):
print (self.account)
class SavingsAccount(BankAccount) :
def ZakatDeduction(self,amount):
self.account=amount*0.25
print(self.account)
class NonTaxFilerAccount(BankAccount):
def withdraw(self, amount):
self.account -= amount*(0.2)
print(self.account)
x = BankAccount(700)
x.balance()
y=BankAccount
y.SavingsAccount(700)
z=BankAccount
z.withdraw(70)

I think you have several problems. Basically your implementation of the BankAccount, SavingsAccount, and NonTaxFilerAccount classes are structurally correct. However:
Since the instructions say to reduce the account balance by 2.5% every time the ZakatDeduction is called, you should update the method to remove the amount as follows:
def ZakatDeduction(self):
self.account -= self.account*0.025
print(self.account)
Since the instructions say to reduce the account balance by an additional 2% of the withdrawn amount when a NonTaxFiler makes a withdraw3al you should update the NonTaxFiler withdraw method as follows:
def withdraw(self, amount):
self.account -= amount*(1.02)
print(self.account)
Employing these classes to create separate accounte with 700 balance should be as follows:
BA = BankAccount(700) #Create a base account
SA = SavingAccount(700) #Create a Savings Account
NTF = NonTaxFiler(700) #Create a NonTaxFilerAccount
performing the following then yields:
BA.withdraw(25)
BA.balance()
675
SA = SavingsAccount(700)
SA.ZakatDeduction()
682.5
NTF = NonTaxFilerAccount(700)
NTF.withdraw(25)
674.5

The attribute error is correct, and in fact its message tells you the issue. Your classes are ok. The error is in the usage. You have:
y=BankAccount
y.SavingsAccount(700)
This means that the y variable now refers to the BankAccount class. The next line attempts to call y.SavingsAccount, and the BankAccount class does not have a method called SavingsAccount.
Did you perhaps mean:
y = SavingsAccount(700)
Note that python is whitespace specific. While technically valid, for readability, you should use the same level of indentation everywhere, but some of your methods are indented by 4, while others by 3

Related

Python: communication between classes and design patterns

I have 4 classes that handle logic for a large project. I have Product that clients buy and i have to bill them. Nevertheless the price of those products is varying greatly according to many variables. I have create a class PriceGenerator that handles the pricing of the products, an Inventory class that checks if the 'Product' is still available and a 'Cart' class that contains a list of 'Product' for a total bill if the client buys many 'Product'
Class PriceGenerator:
def get_price(*args)
Class Product:
def prod_bill()
Class Inventory:
def get_inventory(*args)
Class Cart:
self.list_product = [product1, product2, product3,...]
def cart_bill(*args)
my first option:
pg = PriceGenerator()
pd = Product()
inv = Inventory()
cart = Cart()
I could pass the PriceGenerator and Inventory as argument of Cart
def cart_bill(pg, inv, amount):
bill = 0
for prod in self.list_product:
px = prod.prod_bill(pg)
bill_p = px * min(amount, inv.get_inventory(product_args))
bill += bill_p
Obviously as the number of methods grows in Product, it becomes very complicated to keep track of all the arguments you have to pass. you pass PriceGenerator and Inventory to Cart that are then passed to the product prod.prod_bill(pg), all those nested dependencies are very cumbersome to pass through all the objects and it makes the code much more complicated.
I could call pg and inv without passing it as arguments for example in Product as a global variable
def produce_bill(self):
price = pg.get_price(product_args)
inventory = inv.get_inventory(product_args)
but i really don't like not knowing what are the class/arguments required, necessary for the method.
As the project grows, what design pattern would you suggest?
I would recommend implementing a context object containing anything that is relevant to your process. I am assuming this is about placing an e-commerce order in the following example:
class OrderContext:
price_calculator: PriceCalculator
inventory: Inventory
cart: Cart
Now you can pass this object around for all operations that are involved in your process:
cart.add_product(context, product, amount)
This allows you to add/remove further bits to the context without modifying the signature of all your functions. The drawback is that this has the potential to massively increase the number of dependencies within your application (depending on how disciplined the programmers in your team are).

verbose if condition " if not hasattr(self, '__total'):"

I am following "Fluent Python" to learn Function and Design Pattern:
In chapter 6 example-code/strategy.py
class Order: # the Context
def __init__(self, customer, cart, promotion=None):
self.customer = customer
self.cart = list(cart)
self.promotion = promotion
def total(self):
if not hasattr(self, '__total'):
self.__total = sum(item.total() for item in self.cart)
return self.__total
def due(self):
if self.promotion is None:
discount = 0
else:
discount = self.promotion(self) # <1>
return self.total() - discount
def __repr__(self):
fmt = '<Order total: {:.2f} due: {:.2f}>'
return fmt.format(self.total(), self.due())
I am very confused about:
def total(self):
if not hasattr(self, '__total'):
self.__total = sum(item.total() for item in self.cart)
return self.__total
What's the purpose of if condition here? I guess it could be more readable if:
def total(self):
return sum(item.total() for item in self.cart)
What's the key point I missed? could you please provide any hints?
What happens if you call total more than once? If self.cart hasn't changed, then you're needlessly recalculating the total, a potentially expensive operation.
You're checking if you've already stored the value. If you haven't you calculate it, but if you have you simply return the stored value without recalculating.
As an aside, I would expect name mangling to make your life difficult here because of the double underscore at the beginning of __total. You may want to consider switching to a single underscore.

Python function to send things between 2 users?

I am trying to learn python ( and programming in general). For now I am trying to make a simple bank where users can send/deposit/withdraw money.
I have already created the deposit and withdraw functions and are working. Now I am totally confused on how to write the send function as the user will be sending money and the other will be receiving the money.
Should I write 2 seperate functions fo send and receive, but then how to trigger both in the same time? ( another function containing both) ?
I hope you can help me with this,
so far this is my code:
Classes:
class Account(object):
def __init__(self, name, account_number, initial_amount):
self.name = name
self.no = account_number
self.balance = initial_amount
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
self.balance -= amount
def dump(self):
s = '%s, %s, balance: %s' % \
(self.name, self.no, self.balance)
print s
def get_balance(self):
print(self.balance)
def send(self, sender, receiver, amount):
self.sender = sender
self.receiver = receiver
self.balance -= amount
main.py:
from classes.Account import Account
a1 = Account('John Doe', '19371554951', 20000)
a2 = Account('Jenny Doe', '19371564761', 20000)
a1.deposit(1000)
a1.withdraw(4000)
a2.withdraw(10500)
a2.withdraw(3500)
a1.get_balance()
I know this may be basic, but I hope I can get help here.
Thank you
You already have deposit and withdraw methods so you might as well use them.
Transferring money is essentially withdrawing from one account and depositing it in another.
This can be implemented with a static method that accepts 2 accounts and the amount which will encapsulate the idea of "transfer":
class Account:
.
.
.
#staticmethod
def transfer(from_account, to_account, amount):
from_account.withdraw(amount)
to_account.deposit(amount)
# TODO perhaps you will want to use a try-except block
# to implement a transaction: if either withdrawing or depositing
# fails you will want to rollback the changes.
usage:
from classes.Account import Account
a1 = Account('John Doe', '19371554951', 20000)
a2 = Account('Jenny Doe', '19371564761', 20000)
print(a1.balance)
print(a2.balance)
Account.transfer(a1, a2, 10)
print(a1.balance)
print(a2.balance)
# 20000
# 20000
# 19990
# 20010

How to change instance methods of a super class in subclasses?

While i was doing exercises on inheritance concept of OOP, a question appeared in my mind, the question is about the below exercise:
import random
import time
class Character():
Character_list = []
def __init__(self, name, life=100, power=100):
self.name = name
self.life = life
self.power = power
def print(self):
print("\n{}\n{} character details:".format("-" * 80, self.name))
print("Name: {}\nLife: {}\nPower: {}\n{}".format(self.name, self.life, self.power, "-" * 80))
def attack(self):
attack = random.randrange(self.power + 1)
if self.power <= 0:
print("{} can't attack because power is empty".format(self.name))
return False
else:
print("{} is attacking...".format(self.name))
for i in range(80):
time.sleep(0.05)
print("-", end="", flush=True)
self.power -= attack
print("\n- Spent power: {}".format(attack))
print("- Remaining power: {}\n".format(self.power))
if attack == 0:
print("{} missed.".format(self.name))
return attack
def attacked(self, attack):
if attack != 0:
print("{} injured.".format(self.name))
self.life -= attack
print("- Remaining life: {}".format(self.life))
if self.life <= 0:
print("{} died.".format(self.name))
self.Character_list.remove(self)
class Barbarian(Character):
Character_list = []
class Wizard(Character):
Character_list = []
The question i would like to ask about the above codes is: There’s a super class called Character. The later created Wizard and Barbarian subclasses inherit all the functions of the super class. Suppose to be that the name of the power instance attribute from the super class is different for each subclass. For example, for the Barbarian subclass, it is strength, and for the Wizard subclass it is magic instead of power.
Should instance methods of the super class be copied to the subclasses, in order that each subclass uses the instance attribute of its own class instead of the power name, the super class instance methods should be renamed in the subclasses with their names changed? Or, for the action I want to do, how would you suggest a method other than rewriting the super class instance methods to subclasses by changing the name of the power? Thank you.
Full disclosure - I'm a c# dev trying to answer a python question (so this answer may turn out to be unintentionally hilarious...)
The two easiest avenues I can think of:
A) Declare a variable in your base class and then change your constructor to require a 'PowerWord' be passed in (and then put its value into your variable.) Any time anything tries to declare a new instance of some sort of character, they need to pass in what the PowerWord for that character will be (though I'd imagine you'd just have the derived classes pass it in.)
B) Use an abstract property. This basically says, "Hey, if you want to write a class that derives from me, you'd better have a 'PowerWord' property." Then, this lets you use that PowerWord up in the base class, without having to specify exactly what the PowerWord is (because every derived class will have an answer to what it's supposed to be.)
... anyway, yeah - you had the right idea. You should definitely not copy/paste those functions into each derived class just to change 'power' to 'strength' or to 'magic'.

Understanding the actor model by modeling a bank

I'm trying to understand how the actor model works by modeling a bank. First, here's some code illustrating why we need models for concurrent systems:
import time
from threading import Thread
bank = {'joe': 100}
class Withdrawal(Thread):
"""
Models a concurrent withdrawal for 'joe'. In this example, 'bank'
is a shared resource not protected and accessible from any thread.
Args:
amount (double) how much to withdraw
sleep (bool) config to sleep the thread during the withdrawal
"""
def __init__(self, amount, sleep = False):
self.amount = amount
self.sleep = sleep
Thread.__init__(self)
def run(self):
"""
Overrides method in Thread.
Returns: void
"""
balance = bank['joe']
if balance >= self.amount:
if self.sleep:
time.sleep(5)
bank['joe'] -= self.amount
t1 = Withdrawal(80, True)
t2 = Withdrawal(80)
t1.start()
t2.start()
After running the code, the balance for 'joe' should be -60 after five seconds. This is because bank is unprotected from concurrent access, and pausing for five seconds during concurrent execution means that we can't guarantee that the data won't be accessed at different states. In this case, the first thread accesses the bank after the second thread has finished withdrawing but doesn't check that a withdrawal is still possible. As a result the account goes negative.
If we model the bank and withdrawals as actors, we can protect access to the account since its state is managed on a different thread that's separate from those trying to withdraw from it.
from queue import Queue
from threading import Thread
import time
import random
class Actor(Thread):
"""
Models an actor in the actor model for concurrent computation
see https://en.wikipedia.org/wiki/Actor_model for theoretical overview
Args:
handles (dict) mapping of public methods that are callable
on message data after message has been read
"""
def __init__(self, handles):
self.handles = handles
self.mailbox = Queue()
Thread.__init__(self, daemon=True)
def run(self):
"""
Overrides method in Thread. Once the thread has started,
we listen for messages and process one by one when they are received
Returns: void
"""
self.read_messages()
def send(self, actor, message):
"""
Puts a Message in the recipient actor's mailbox
Args:
actor (Actor) to receive message
message (Message) object to send actor
Returns: void
"""
actor.mailbox.put(message)
def read_messages(self):
"""
Reads messages one at a time and calls the target class handler
Returns: void
"""
while 1:
message = self.mailbox.get()
action = message.target
if action in self.handles:
self.handles[action](message.data)
class Message:
"""
Models a message in the actor model
Args:
sender (Actor) instance that owns the message
data (dict) message data that can be consumed
target (string) function in the recipient Actor to we'd like run when read
"""
def __init__(self, sender, data, target):
self.sender = sender
self.data = data
self.target = target
class Bank(Actor):
"""
Models a bank. Can be used in concurrent computations.
Args:
bank (dict) name to amount mapping that models state of Bank
"""
def __init__(self, bank):
self.bank = bank
Actor.__init__(self, {'withdraw': lambda data: self.withdraw(data)})
def withdraw(self, data):
"""
Action handler for 'withdraw' messages. Withdraw
if we can cover the requested amount
Args:
data (dict) message data
Returns: void
"""
name, amount = data['name'], data['amount']
if self.bank[name] >= amount:
if data['sleep']:
time.sleep(2)
self.bank[name] -= amount
class Withdrawal(Actor):
"""
Models a withdrawal. Can be used in concurrent computations.
Args:
bank (Bank) shared resource to transact with
sleep (bool) config to request that the bank sleep during a withdrawal
"""
def __init__(self, bank, sleep=False):
self.bank = bank
self.sleep = sleep
Actor.__init__(self, {})
def withdraw(self, name, amount):
"""
Wrapper for sending a withdrawl message
Args:
name (string) owner of the account in our bank
amount (double) amount we'd like to withdraw
Returns: void
"""
data = {'sleep': self.sleep, 'name': name, 'amount': amount}
Actor.send(self, self.bank, Message(self, data, 'withdraw'))
Let's now test:
bank = Bank({'joe': 100})
bank.start()
actors = []
for _ in range(100):
a = Withdrawal(bank, random.randint(0, 1))
a.start()
actors.append(a)
for a in actors:
a.withdraw('joe', 15)
Is this understanding correct? Even though the bank sleeps during withdrawals, no simultaneous withdrawal can corrupt the data because it's managed on a different thread than the withdrawals.
Simultaneous withdrawal can no longer occur, true, but it's because the withdraw messages are handled serially, not concurrently, by the single Bank thread inside the Bank.read_messages loop. This means that the sleep commands are also executed serially; the entire message queue is going to stall and yield control for 2 seconds whenever the bank has to sleep during a withdrawal. (Given the Bank's modeled action, this is essentially unavoidable).
If access to an object is isolated to a single thread it is generally considered thread safe.
The other actors cannot access the bank's storage directly but only send messages requesting a withdrawal, so updates only occur in the bank thread and the check-and-set race condition in the original design is eliminated.

Resources