Basic ATM machine - calling a class within another class

I’m working on a script which emulates a basic bank account that can deposit and withdraw money like an ATM. I made a forum thread earlier which was helpful but since then I’ve added new features such as an interest calculator as well as a date and time register. Taking my script to the next level, what I am struggling with is assembling all the unique information for each transaction to generate into a “confirmation number”. This is the task I am working on now.

In greater detail, here is a snippet of the description from the exercise provided by the instructor for a non-credit as a hobby Udemy course:

So for example, the confirmation should look something like this:
D-140568-20190315145900-124

…where:

  • “D” represents deposit
  • ”140568” represents the account number (which is arbitrary)
  • ”20190315145900” represents a concatenated local timestamp (Specific timezone: MST)
  • “124” is the transaction ID. The instructor specifies it starts at zero and with each transaction, it should increment by 1.

That is the breakdown and structure of the confirmation number that I am trying to have my script generate for each transaction.

Here is my script:

from datetime import datetime
from pytz import timezone
 
class TimeZone:
 
   def __init__(self, locality='US/Mountain'):
       self.tz = datetime.now(timezone(locality))
       self.readable_format = '%Y-%m-%d %H:%M:%S %Z%z'
       print(f'The date and time: {self.tz.strftime(self.readable_format)}')
       self.transaction_time_id_format = '%Y%m%d%H%M%S'
       print(f'The date and time condensed: {self.tz.strftime(self.transaction_time_id_format)}')
 
class Account:
  
   interest = 0.005 # Percent
 
   def __init__(self, first_name, last_name, account_num=1400, starting_balance=0.00):
       self.first_name = first_name
       self.last_name = last_name
       self.full_name = f'{first_name} {last_name}'
       self.account_num = account_num
       self.balance = starting_balance
       self.transaction_id = 0
 
   def deposit(self, amount):
       self.balance += amount
       print(f'D-{self.account_num}-{TimeZone(self.transaction_time_id_format)}-{self.transaction_id+1}') # problem line
  
   def withdraw(self, amount):
       if amount > self.balance:
           raise ValueError('Transaction declined. Insufficient funds. Please deposit some more $$$ first.')
       self.balance -= amount
 
   def pay_interest(self):
       monthly_rate = self.interest/12
       monthly_sum = monthly_rate * self.balance
       return monthly_sum + self.balance
 
   def __repr__(self):
       """Return a string that represents the account."""
       return f"{self.__class__.__name__}({self.last_name}, {self.first_name}, balance={self.balance})"

Here is me playing around with my classes in my REPL with output:

$ bpython
bpython version 0.22.1 on top of Python 3.10.2 /usr/bin/python
>>> import script
>>> time_instance = script.TimeZone()
The date and time: 2022-03-09 22:58:27 MST-0700
The date and time: 20220309225827

Above I am instantiating the TimeZone class. As you can see with the print statements, it’s working so far.

But when I attempt to instantiate the Account class and perform a deposit(), I get a traceback directing my attention to line 27:

>>> BA = script.Account('Winston', 'Smith')
>>> BA.deposit(100)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    BA.deposit(100)
  File "/home/<user>/dev/projects/python/2018-and-2020/Udemy-Fred-Baptiste-OOP/script.py", line 27, in deposit
    print(f'D-{self.account_num}-{TimeZone(self.transaction_time_id_format)}-{se
lf.transaction_id+1}')
AttributeError: 'Account' object has no attribute 'transaction_time_id_format'

At line 27 I am trying to create the confirmation number using an f-string. The problem is with the way I am calling the TimeZone class embedded within the Account class within the deposit() method.

So my first question is: How do I properly interpolate the transaction_time_id_format call (with a ‘now’ timestamp for the MST time zone) from the TimeZone class into the deposit() method that exists in the other Account class?

For my humble purposes here right now with this thread, I am first just trying to build a basic f-string with the matching format of the confirmation number embedded within the method. Although I realize that I am kind of missing the point and have mostly strayed away from the larger picture that the instructor is asking for.

What the instructor really wants is another completely separate method elsewhere in the script that is likely primarily responsible for generating confirmation numbers which can then print transaction details individually or in aggregate. That is probably the end game. But at this point it’s not clear to me what that next step might be in terms of writing an outside mechanism to be triggered during every deposit() / withdrawal() / pay_interest() method call to generate confirmation numbers.

To assist with this next level challenge, any further hints, advice, guidance, and tips are welcome. :grin:

In the deposit method of Account, you have:

       print(f'D-{self.account_num}-{TimeZone(self.transaction_time_id_format)}-{self.transaction_id+1}') # problem line

This portion of that statement looks for a transaction_time_id_format attribute in the Account instance:

self.transaction_time_id_format

That is because self refers to that instance. But, in fact, transaction_time_id_format is an attribute connected with your TimeZone class, and not with the Account class. Within the Account class definition, you need to instantiate a TimeZone instance and use that. Access the transaction_time_id_format attribute of that TimeZone instance.

You could add something like this to the __init__ method of the Account class:

       self.tzone = TimeZone()

Then, in the deposit method of the Account class, the print statement would be changed to this:

       print(f'D-{self.account_num}-{self.tzone.transaction_time_id_format}-{self.transaction_id+1}')

Notice this within that revised statement:

self.tzone.transaction_time_id_format

There, self refers to the Account instance. Then, tzone refers to the TimeZone instance that was created within the __init__ method. Then, transaction_time_id_format refers to the transaction_time_id_format attribute of that TimeZone instance.

The above might not be exactly what you need, but you can try that out, and revise it as necessary.

EDIT (March 11, 2022):

This might be a little closer to what you are trying to do with that print statement:

       print(f'D-{self.account_num}-{self.tzone.tz.strftime(self.tzone.transaction_time_id_format)}-{self.transaction_id+1}')

Example output:

D-1400-20220311051033-1

The print statement suggested above is rather messy. As a next step, the code could be reorganized a bit, in order to better benefit from the capabilities of OOP. That would allow for a much cleaner computation of the confirmation number within the Account class.

Arrange for the TimeZone class to be able to return to you what is really needed to build a confirmation number. That information is currently contained in this statement:

       print(f'The date and time condensed: {self.tz.strftime(self.transaction_time_id_format)}')

You could add a method to that class that instead returns that information, rather than just print it. Perhaps name the method condense. Then, from the Account class, you could call that method from the TimeZone instance, in order to incorporate the condensed date and time into the confirmation number.

EDIT (March 12, 2022):

Below is an example of a condensed method that could be included in the definition of the TimeZone class:

   def condensed(self):
       return f'{self.tz.strftime(self.transaction_time_id_format)}'

Note the use of the name condensed, rather than the name condense suggested above. The name condensed seems more in keeping with the flavor of Python naming conventions and the subtleties of natural language semantics, since the method offers a condensed version of the date and time, while not altering the source.

The condensed method could be called from an instance of the TimeZone class that resides within an instance of the Account class, in order to supply one of the components for assembling the confirmation number, as described in the original post.

Please consider trying this out. If you do so, we would enjoy learning of the result.

Thank you @Quercus for your detailed reply.

Based on your feedback, here is what I changed in my script:

  • Added your recommended condensed() method to the TimeZone class
  • Added self.tzone = TimeZone() attribute declaration below __init__ of the Account() class
  • Added print(f'D-{self.account_num}-{self.tzone.condensed()}-{self.transaction_id+1}') to the deposit() method.

In my REPL, after I import the script, instantiate Account(), the TimeZone() is initialized as well. When I deposit $100, the transaction ID and all the other meta data appears perfectly.

Here is my REPL output:

$ bpython
bpython version 0.22.1 on top of Python 3.10.2 /usr/bin/python
>>> import timezone_interest3
>>> BA = timezone_interest3.Account('Winston', 'Smith')
The date and time: 2022-03-14 03:56:17 MDT-0600
The date and time condensed: 20220314035617
>>> BA.deposit(100)
D-1400-20220314035617-1
>>> BA.deposit(100)
D-1400-20220314035617-1
>>> 

I’ve got some more ideas for future work and I will be sure to return here if I have and further questions. Thanks again Quercus!

For reference, here is my script in full as of right now:

from datetime import datetime
from pytz import timezone
 
class TimeZone:
 
   def __init__(self, locality='US/Mountain'):
       self.tz = datetime.now(timezone(locality))
       self.readable_format = '%Y-%m-%d %H:%M:%S %Z%z'
       print(f'The date and time: {self.tz.strftime(self.readable_format)}')
       self.transaction_time_id_format = '%Y%m%d%H%M%S'
       print(f'The date and time condensed: {self.tz.strftime(self.transaction_time_id_format)}')
  
   def condensed(self):
      return f'{self.tz.strftime(self.transaction_time_id_format)}'
 
class Account:
  
   interest = 0.005 # Percent
 
   def __init__(self, first_name, last_name, account_num=1400, starting_balance=0.00):
       self.first_name = first_name
       self.last_name = last_name
       self.full_name = f'{first_name} {last_name}'
       self.account_num = account_num
       self.balance = starting_balance
       self.transaction_id = 0
       self.tzone = TimeZone()
 
   def deposit(self, amount):
       self.balance += amount
       print(f'D-{self.account_num}-{self.tzone.condensed()}-{self.transaction_id+1}') # problem line
  
   def withdraw(self, amount):
       if amount > self.balance:
           raise ValueError('Transaction declined. Insufficient funds. Please deposit some more $$$ first.')
       self.balance -= amount
 
   def pay_interest(self):
       monthly_rate = self.interest/12
       monthly_sum = monthly_rate * self.balance
       return monthly_sum + self.balance
 
   def __repr__(self):
       """Return a string that represents the account."""
       return f"{self.__class__.__name__}({self.last_name}, {self.first_name}, balance={self.balance})"

edit: sp

1 Like