Código ATM para saldo da conta, retiradas e depósitos
Sou relativamente novo em Python e programação em geral, e decidi que este seria um bom projeto de prática. Este também foi meu primeiro projeto envolvendo classes e objetos, então mantive sua implementação um pouco básica apenas para ter uma ideia. Eu apreciaria qualquer crítica construtiva sobre como eu poderia melhorar coisas como minha legibilidade, eficiência e se havia maneiras mais fáceis de fazer as coisas.
A saída pretendida do programa é simular a interação com um banco ou caixa eletrônico muito básico. Eu não tinha intenção de armazenar nenhuma informação de conta em um arquivo separado, então cada pin de conta é completamente arbitrário e serve apenas como um meio para tornar a simulação um pouco mais realista. O programa é totalmente funcional, tanto quanto eu posso dizer, embora haja a possibilidade de alguns bugs escapando pelas rachaduras
import random
import time
class Accounts:
# Defining Account instance variables.
def __init__(self, pin, balance, annualInterestRate=3.4):
self.pin = pin
self.balance = balance
self.annualInterestRate = annualInterestRate
# Class function to return the monthly interest rate.
def getMonthlyInterestRate(self):
return self.annualInterestRate / 12
# class function to calculate difference between the balance and the amount withdrawn.
def withdraw(self, amount):
self.balance -= amount
# class function to calculate the sum between the balance and the amount deposited.
def deposit(self, amount):
self.balance += amount
# Class function to calculate the product of the balance and the annual interest rate.
def getAnnualInterest(self):
return self.balance * self.annualInterestRate
# Class function to calculate the product of the balance and the monthly interest rate.
def getMonthlyInterest(self):
return self.balance * self.getMonthlyInterestRate()
# Revieves pin from user input and validates input.
def getAccountPin():
while True:
pin = input("\nEnter four digit account pin: ")
try:
pin = int(pin)
if pin >= 1000 and pin <= 9999:
return pin
else:
print(f"\n{pin} is not a valid pin... Try again")
except ValueError:
print(f"\n{pin} is not a vaild pin... Try again")
# Recieves user input for option selection and validates selection.
def getSelection():
while True:
selection = input("\nEnter your selection: ")
try:
selection = int(selection)
if selection >= 1 and selection <= 4:
return selection
else:
print(f"{selection} is not a valid choice... Try again")
except ValueError:
print(f"{selection} is not a valid choice... Try again")
# Returns the current working accounts balance.
def viewBalance(workingAccount):
return workingAccount.balance
# Recieves user input and validates if input is either yes, y, no, or n.
def correctAmount(amount):
while True:
answer = input(f"Is ${amount} the correct ammount, Yes or No? ") try: answer = answer.lower() if answer == "y" or answer == "yes": return True elif answer == "n" or answer == "no": return False else: print("Please enter a valid response") except AttributeError: print("Please enter a valid response") # Recieves user input on amount to withdraw and validates inputed value. def withdraw(workingAccount): while True: try: amount = float(input("\nEnter amount you want to withdraw: ")) try: amount = round(amount, 2) if amount > 0 and ((workingAccount.balance) - amount) > 0: answer = correctAmount(amount) if answer == True: print("Verifying withdraw") time.sleep(random.randint(1, 2)) return amount elif (((workingAccount.balance) - amount) < 0): print("\nYour balance is less than the withdraw amount") elif amount == 0: answer = correctAmount(amount) if answer == True: print("Canceling withdraw") time.sleep(random.randint(1, 2)) return amount else: print("\nPlease enter an amount greater than or equal to 0") except TypeError: print("\nAmount entered is invalid... Try again") except ValueError: print("\nAmount entered is invalid... Try again") # Recieves user input on amount to deposit and validates inputed value. def deposit(workingAccount): while True: try: amount = float(input("\nEnter amount you want to deposit: ")) try: amount = round(amount, 2) if amount > 0: answer = correctAmount(amount) if answer == True: print("Verifying deposit") time.sleep(random.randint(1, 2)) return amount elif amount == 0: answer = correctAmount(amount) if answer == True: print("Canceling deposit") time.sleep(random.randint(1, 2)) return amount else: print("\nPlease enter an amount greater than or equal to 0") except TypeError: print("\nAmount entered is invalid... Try again") except ValueError: print("\nAmount entered is invalid... Try again") # End of program to print out account information and return false to end main loop def exitATM(workingAccount): print("\nTransaction is now complete.") print("Transaction number: ", random.randint(10000, 1000000)) print("Current Interest Rate: ", workingAccount.annualInterestRate) print("Monthly Interest Rate: ", workingAccount.annualInterestRate / 12) print("Thanks for using this ATM") return False def main(): # Creating all accounts possible, could be stored or read from a file/database instead for better functionality overall. accounts = [] for i in range(1000, 9999): account = Accounts(i, 0) accounts.append(account) # ATM Processes loop loop = True while loop == True: pin = getAccountPin() print(pin) # Account session loop while loop == True: # Menu Selection print("\n1 - View Balance \t 2 - Withdraw \t 3 - Deposit \t 4 - Exit ") selection = getSelection() # Getting working account object by comparing pins for acc in accounts: # Comparing user inputted pin to pins created if acc.pin == pin: workingAccount = acc break # View Balance if selection == 1: print(f"\nYour balance is ${viewBalance(workingAccount)}")
# Withdraw
elif selection == 2:
workingAccount.withdraw(withdraw(workingAccount))
print(f"\nUpdated Balance: ${workingAccount.balance}") # Deposit elif selection == 3: workingAccount.deposit(deposit(workingAccount)) print(f"\nUpdated Balance: ${workingAccount.balance}")
# Exit
elif selection == 4:
loop = exitATM(workingAccount)
# Invalid input
else:
print("Enter a valid choice")
if __name__ == "__main__":
main()
Respostas
Boa implementação, algumas sugestões:
não é realista usar o PIN como o ID da conta. Para torná-lo mais realista, você pode pedir o ID da conta primeiro e depois o pin. Inserir o ID da conta seria a simulação de "inserir o cartão no caixa eletrônico".
A função
getAccountPin()solicita entrada do usuário, um nome melhor seriarequestAccountPin()A função
viewBalanceabaixo pode ser um método emAccountsvez de uma função global:def viewBalance(workingAccount): # Returns the current working accounts balance return workingAccount.balancePara simplificar a função,
withdraw(workingAccount)mova os cheques na balança diretamente para dentroAccounts.withdraw. Por exemplo:def withdraw(self, amount): if amount > 0 and self.balance - amount >= 0: self.balance -= amount return True return FalseO mesmo para
deposit(workingAccount), pode ser simplificado movendo parte da lógica paraAccounts.deposit:def deposit(self, amount): if amount > 0: self.balance += amount return True return FalseA classe
Accountscontém as informações de uma única conta, então você pode simplesmente chamá-laAccount
Seu código é bem estruturado em funções curtas e bem nomeadas, isso é ótimo de ver. Aqui estão alguns pontos para melhorar:
verifique a convenção PEP8 sobre a nomenclatura de variáveis. Os nomes das funções e variáveis devem vir a seguir
snake_case, então, em vez dissodef viewBalance(workingAccount):, é melhor usardef view_balance(working_account):Os blocos try-except devem envolver o código relevante o mais firmemente possível. Se você envolver todo o código em uma grande exceção de tentativa, tecnicamente nenhum erro acontecerá, mas às vezes você pode capturar uma exceção que não pretende capturar e ela pode ser tratada de forma inadequada. Por exemplo, em
withdrawfunção você tem dois blocos aninhados try-except com a mesma mensagem. Eles podem ser mesclados e podem (devem) quebrar apenas as linhas relevantes. O bom efeito colateral é que o outro código será menos recuado, o que pode melhorar a legibilidade do código.também há um bug aí. Na primeira condição, você verifica se o valor retirado é maior que zero, mas deve ser maior ou igual .
você está ligando
correctAmount()apenas no caso de o valor ser aceitável ou zero, mas deve ser chamado mesmo se o saldo estiver incorreto (o que pode acontecer mais provavelmente devido ao valor inserido incorretamente). E, nesse caso, em vez de repeti-lo três vezes, você pode chamá-lo apenas uma vez antes de fazer a lógica de ramificação.
def withdraw(working_account):
while True:
# try-except block should be wrapping the relevant code as tightly as possible
try:
amount = float(input("\nEnter amount you want to withdraw: "))
amount = round(amount, 2)
except (ValueError, TypeError):
print("\nAmount entered is invalid... Try again")
continue
# dont repeat `correct_amount` in multiple places
if not correct_amount(amount):
continue
# the second condition should be >= instead of >
if amount > 0 and (working_account.balance - amount) >= 0:
print("Verifying withdraw")
time.sleep(random.randint(1, 2))
return amount
elif (working_account.balance - amount) < 0:
print("\nYour balance is less than the withdraw amount")
elif amount == 0:
print("Canceling withdraw")
time.sleep(random.randint(1, 2))
return amount
else:
print("\nPlease enter an amount greater than or equal to 0")
- os itens a seguir são apenas pequenos truques que você mesmo descobrirá com o tempo, mas aqui está um caminho de atalho: em Python, você não precisa comparar valores explicitamente. Tudo, exceto
0,None,"",Falsee coleções vazias é avaliada paraTrue, assim que sua relação pode ser encurtado:
while loop == True:
do_something()
# you can use only `while loop:` instead:
while loop:
loop = "any value, the condition will still work"
- da mesma forma, se você precisar comparar um valor de retorno obtido da função, mas não trabalhar mais com ele, não será necessário atribuí-lo a uma variável temporária:
answer = correctAmount(amount)
if answer == True:
print("Verifying withdraw")
# you can write this instead:
if correct_amount(amount):
print("Verifying withdraw")
- as comparações podem ser encadeadas:
if pin >= 1000 and pin <= 9999:
return pin
# you can use following:
if 1000 <= pin <= 9999:
return pin
Percebo algumas áreas que acho que podem ser melhoradas:
Geralmente, o código Python usa caixa de cobra em vez de caixa de camelo para formatar nomes de variáveis. Então, por exemplo:
def getMonthlyInterestRate(self): return self.annualInterestRate / 12Se tornaria:
def get_monthly_interest_rate(self): return self.annualInterestRate / 12Mas isso realmente não é muito importante. Contanto que você permaneça consistente com qualquer um deles, seu código será legível.
Em
getAccountPin, tambémif pin >= 1000 and pin <= 9999:pode ser simplificado1000 <= pin <= 9999. Isso também pode ser feito para suas outras condicionais no intervalo (por exemplo:selection >= 1 and selection <= 4aif 1 <= selection <= 4:).Não tenho certeza por que
viewBalanceprecisa existir? Basta obter o saldo da conta de trabalho diretamente usando.balance, sem necessidade de uma função getter aqui. Em geral, é considerada uma prática recomendada evitar o uso de getters quando possível.Em
depositewithdraw, você não precisa detry/exceptblocos aninhados .exceptpode levar um ou mais erros para interceptar: No seu casoexcept (ValueError, TypeError). Isso tornará seu código muito mais limpo.Eu acho
depositewithdrawdeveria ser métodos deAccountsobjetos, não métodos autônomos. SeAccountsrepresenta contas bancárias, faz sentido associar a ação de sacar e depositar dinheiro com as contas bancárias.depositnunca usa seu argumentoworkingAccount.Evite usar o
if var == True. É muito mais simples e mais limpa fazer apenasif varpara testar se ou nãovaréTrue.
Você começou bem e já tem vários comentários úteis em outras avaliações.
Muito do código se preocupa com os detalhes de obtenção de entrada do usuário - e é tedioso e repetitivo. Eu encorajo você a pensar sobre como alguém pode generalizar o processo de entrada do usuário: exibir mensagem; obter entrada; converter a resposta em um valor significativo; valide o valor; imprimir uma mensagem e / ou retornar um valor. Aqui está um esboço da ideia:
def getAccountPin():
return get_user_input(
message = 'Enter four digit account pin',
converter = int,
validator = (lambda x: x in range(1000, 10000)),
)
def getSelection():
return get_user_input(
message = 'Enter your selection',
converter = int,
validator = (lambda x: x in range(1, 5)),
)
def get_user_input(message, converter, validator):
while True:
reply = input('\n' + message + ': ')
try:
value = converter(reply)
except Exception:
print('Invalid entry. Try again')
if validator(value):
return value
else:
print('Bad value. Try again')
Algumas de suas funções atuais se encaixam nessa abordagem, mas outras apresentam alguns desafios interessantes (e provavelmente solucionáveis). Boa sorte!