4

I'm learning python useing How to think like a computer scientist: Learn python 3.

I'm learning OOP and have put together some code to answer a question in the book, but I feel like I should have done something else.

The code in question is incremental(), which the goal of is to increase the object's values. Now my final solution was to make my method a copy of the initializer method and just add on the time right there.

This feels sloppy:

class MyTime:

    def __init__(self, hrs=0, mins=0, secs=0,):
       """ Create a new MyTime object initialized to hrs, mins, secs.
           The values of mins and secs may be outside the range 0-59,
           but the resulting MyTime object will be normalized.
       """

       # calculate total seconds to represent
       totalsecs = hrs*3600 + mins*60 + secs
       self.hours = totalsecs // 3600        # split in h, m, s
       leftoversecs = totalsecs % 3600
       self.minutes = leftoversecs // 60
       self.seconds = leftoversecs % 60

    def incerment(self,t):
       # increase the time by t amount
       totalsecs = self.hours * 3600 + self.minutes * 60 + self.seconds + t
       self.hours = totalsecs // 3600        # split in h, m, s
       leftoversecs = totalsecs % 3600
       self.minutes = leftoversecs // 60
       self.seconds = leftoversecs % 60


t1 = MyTime(5,5,5)
t2 = MyTime(10,10,10)
t3 = MyTime(12,12,12)

print('before:',t1)
t1.incerment(100)
print('after:',t1)

So how about it?
Is there a way to clean this up?

4 Answers 4

4

That feeling like you should have done something else it's beacuse hours, minutes, seconds are properties.

You don't really need those values stored as attributes of your object, you just want to be able to access those values when you need.

Calling something like:

>>> t1.hours
5

So, let's rewrite your example using property:

class MyTime:
    def __init__(self, hrs=0, mins=0, secs=0):
       self.totalsecs = hrs*3600 + mins*60 + secs

    @property
    def hours(self):
       return self.totalsecs // 3600

    @property
    def minutes(self):
        return self._get_leftoversecs() // 60

    @property
    def seconds(self):
        return self._get_leftoversecs() % 60

    def _get_leftoversecs(self):
        return self.totalsecs % 3600

    def increment(self, t):
        self.totalsecs += t

Usage example:

>>> t1 = MyTime(5,5,5)
>>> t1.hours
5
>>> t1.hours()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
>>> t1.seconds
5
>>> t1.totalsecs
18305
>>> t1.increment(10)
>>> t1.seconds
15
>>> t1.totalsecs
18315

I don't know if you noticed but you don't actually need the increment function anymore:

>>> t1.totalsecs += 10
>>> t1.totalsecs
18325

I know that property must be a little ahead of what you're doing, but I thought it would have been worth an example.

Edit: As Lattyware noticed there's no need to make totalsecs a property too.

To quote his comment: The great thing about Python's properties is you don't need to turn everything into getters and setters to keep a consistent interface like you do in some languages.

There might be an advantage in setting totalsecs as a property (read-only) only if for some reason you want to hide the internal implementation of MyTime (obviously reintegrating the increment() method).

Sign up to request clarification or add additional context in comments.

2 Comments

Agreed in all cases except your turning totalsecs into a property - it functions exactly as a normal attribute would, so why put in the extra functions? Just leave it as an attribute unless you need to change it. The great thing about Python's properties is you don't need to turn everything into getters and setters to keep a consistent interface like you do in some languages.
@Lattyware: Good point. I just got a little carried away with property :) I've updated my answer.
1

You can try this;

   # calculate total seconds to represent
   self.totalsecs = hrs*3600 + mins*60 + secs
   self.set_times()

def incerment(self, t):
    # increase the time by t amount
   self.totalsecs = self.hours * 3600 + self.minutes * 60 + self.seconds + t
   self.set_times()

def set_times(self):
   self.hours = self.totalsecs // 3600        # split in h, m, s
   leftoversecs = self.totalsecs % 3600
   self.minutes = leftoversecs // 60
   self.seconds = leftoversecs % 60

Comments

0

The main change is that you should use incerment in __init__, as your init contains substantially the same code.

Comments

0

I would probably factor out the access to 'totalsecs' into a property and then use that:

class MyTime:

    def __init__(self, hrs=0, mins=0, secs=0,):
       """ Create a new MyTime object initialized to hrs, mins, secs.
           The values of mins and secs may be outside the range 0-59,
           but the resulting MyTime object will be normalized.
       """
       self.totalsecs = hrs*3600 + mins*60 + secs

    @property
    def totalsecs(self):
        return self.hours * 3600 + self.minutes * 60 + self.seconds

    @totalsecs.setter
    def totalsecs(self, totalsecs):
       self.hours = totalsecs // 3600        # split in h, m, s
       leftoversecs = totalsecs % 3600
       self.minutes = leftoversecs // 60
       self.seconds = leftoversecs % 60

    def increment(self,t):
       # increase the time by t amount
       self.totalsecs += t

Of course if you do it that way you don't really need the increment() method any more. Plus if you later decide to switch to Rik Poggi's solution and just store the totalsecs value and make the hours, minutes, seconds into properties instead you have minimal changes.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.