Código ATM para saldo da conta, retiradas e depósitos

Sep 10 2020

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

11 Marc Sep 10 2020 at 10:24

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 em Accountsvez de uma função global:

    def viewBalance(workingAccount):
        # Returns the current working accounts balance
        return workingAccount.balance
    
  • Para simplificar a função, withdraw(workingAccount)mova os cheques na balança diretamente para dentro Accounts.withdraw. Por exemplo:

    def withdraw(self, amount):
        if amount > 0 and self.balance - amount >= 0:
            self.balance -= amount
            return True
        return False
    
  • O mesmo para deposit(workingAccount), pode ser simplificado movendo parte da lógica para Accounts.deposit:

    def deposit(self, amount):
        if amount > 0:
            self.balance += amount
            return True
        return False
    
  • A classe Accountscontém as informações de uma única conta, então você pode simplesmente chamá-laAccount

10 yedpodtrzitko Sep 10 2020 at 10:32

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 disso def 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 para True, 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
6 ChristianDean Sep 10 2020 at 10:41

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 / 12
    

    Se tornaria:

    def get_monthly_interest_rate(self):
        return self.annualInterestRate / 12
    

    Mas 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ém if pin >= 1000 and pin <= 9999:pode ser simplificado 1000 <= pin <= 9999. Isso também pode ser feito para suas outras condicionais no intervalo (por exemplo: selection >= 1 and selection <= 4a if 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 deposite withdraw, você não precisa de try/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 deposite withdrawdeveria ser métodos de Accountsobjetos, não métodos autônomos. Se Accountsrepresenta contas bancárias, faz sentido associar a ação de sacar e depositar dinheiro com as contas bancárias.

  • depositnunca usa seu argumento workingAccount.

  • Evite usar o if var == True. É muito mais simples e mais limpa fazer apenas if varpara testar se ou não varé True.

6 FMc Sep 11 2020 at 10:32

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!