title: Is the example below considered efficient? And if not, how so can it be better?
body: <p>I am new to programming and object-oriented programming.</p>
  
  <p><strong>Below is a portion of the code in python that is running just fine as intended.</strong> I have hard-coded some variable for testing purposes at the open_bank_account().</p>
  
  <p><strong>Question: Would be considered fairly optimal and efficient? If not, where can I look to improve the code?</strong></p>
  
  <pre><code>class Bank_Account:
      def __init__(self, account_id, account_pin, account_balance):
          self._account_id = account_id
          self._account_pin = account_pin
          self._account_balance = account_balance
          self._individual_bank_account_dicitionary = {}
          self._individual_bank_account_dicitionary[account_id] = {"Pin": account_pin, "Balance": account_balance}
  
      def get_bank_account_balance(self, account_id):
          return "Balance: ${:.2f}".format(self._individual_bank_account_dicitionary[account_id]["Balance"])
  
      def __str__(self):
          return "{}".format(self._individual_bank_account_dicitionary)
  
  class Bank:
      def __init__(self, bank_name):
          self._bank_name = bank_name
          self._bank_dicitionary = {}
  
      def update_bank_dictionary(self, bank_account):
          self._bank_dicitionary[bank_account._account_id] = {"Pin": bank_account._account_pin, "Balance": bank_account._account_balance}
  
      # 1. A method to return the dictionary of bank accounts of the object from class Bank.
      # 2. In this case, only bank_1 from main() is the object argument.      
      def get_bank_dictionary(self):
          return self._bank_dicitionary
  
      # 1. Method to create object bank_account only when needed.
      def get_bank_account(self, account_id):
          account_pin = self._bank_dicitionary[account_id]["Pin"]
          account_balance = self._bank_dicitionary[account_id]["Balance"]
          bank_account = Bank_Account(account_id, account_pin, account_balance)
          return bank_account
  
      # 1. This is used to convert the dictionary into a string for printing purposes.
      def string_bank_dictionary(self):
          string_list = ["account ID: {} \naccount Pin: {} \nBank Balance: {} \n".format(key, self._bank_dicitionary[key]["Pin"], self._bank_dicitionary[key]["Balance"])\
                        for key, value in self._bank_dicitionary.items()]
          return "\n".join(string_list) 
  
      def __str__(self):
          return "{}".format(self.string_bank_dictionary())
  
  def open_bank_account(bank):
  #     # Uncomment out when running actual program.
  #     account_id = input("Enter account id here: ")
  #     account_pin = input("Enter account pin here: ")
  #     account_balance = input("Enter account initial balance here: ")
  
      # Comment out when running actual program. 
      # Currently in used for testing purposes.
      account_id = 455
      account_pin = 123
      account_balance = 888
      bank_account = Bank_Account(account_id, account_pin, account_balance)
      bank.update_bank_dictionary(bank_account)
  
      # Comment out when running actual program.
      # Currently in used for testing purposes.
      account_id = 777
      account_pin = 777
      account_balance = 1
      bank_account = Bank_Account(account_id, account_pin, account_balance)
      bank.update_bank_dictionary(bank_account)
  
      # Comment out when running actual program. 
      # Currently in used for testing purposes.   
      account_id = 631
      account_pin = 222
      account_balance = 50
      bank_account = Bank_Account(account_id, account_pin, account_balance)
      bank.update_bank_dictionary(bank_account)
  
      return bank
  
  def check_bank_blanance(bank):
      valid_id_password = False
      temporary_dictionary = bank.get_bank_dictionary()
      while True:
          account_id = int(input("Enter account id here: "))
          account_pin = int(input("Enter account pin here: "))
          for key in temporary_dictionary.keys():
              if account_id == key and temporary_dictionary[account_id]["Pin"] == account_pin:
                  valid_id_password = True
                  bank_account = bank.get_bank_account(account_id)
                  print(bank_account.get_bank_account_balance(account_id))
                  break
          if valid_id_password == True:
              break
          else:
              print("Invalid account id/password. Please try again.")
  
  def main():
      bank_1 = Bank("ABC Bank")
      while True:
          print("Menu \n1. Open bank account \n2. Check balance")
          while True:
              account_choice = int(input("Enter option here: "))
              if account_choice &lt;= 0 and account_choice &gt;= 7:
                  account_choice = int(input("Enter option here: "))
              else:
                  break        
          if account_choice == 6:
              break
          elif account_choice == 1:
              bank_1 = open_bank_account(bank_1)
          elif account_choice == 2:
              balance = check_bank_blanance(bank_1)
  
  main()
  </code></pre>
  
  <p>The program works just fine. I would like to seek pointers on improvement if I code this better and more efficiently.</p>
  
  <p>I have designed and kept only one object in the main(); i.e. bank_1; this contains a dictionary of the multiple bank account information. I have elected to only create the object bank_account at the open_bank_account() and check_bank_blanance() only when needed and these objects disappear once the function is done.</p>
  
  <p><strong>The intention here is to keep as little objects available as possible at the main().</strong></p>
  
  <p>No errors encountered. The program runs just fine; just enter &lt;1>, then enter &lt;2>, then enter &lt;777> followed by &lt;777>. It will print the bank account balance of $1.00 of the account id 777.</p>