Skip to content

Add the ability to compare a timer to another value.#13

Merged
gahjelle merged 5 commits into
realpython:masterfrom
janfreyberg:master
Jan 14, 2020
Merged

Add the ability to compare a timer to another value.#13
gahjelle merged 5 commits into
realpython:masterfrom
janfreyberg:master

Conversation

@janfreyberg
Copy link
Copy Markdown
Contributor

This allows checking whether the last measured time is greater
or less than some 'reference' value.

  • Timer class now has a reference to the last-measured value; initially this is nan
  • comparison operators are defined for Timer objects.

This is useful if you want to display messages if some operation
has taken too long in the past.

For example:

from codetiming import Timer
import time

image_display_timer = Timer()

def _display_hold_message():
    print('loading image...')

@image_display_timer
def display_image():
    if image_display_timer > 0.2:
        _display_hold_message()
    time.sleep(0.5)
    print('here is your image')

This results in a message being displayed when the function is used for the
second time:

>>> display_image()
here is your image
Elapsed time: 0.5051 seconds

>>> display_image()
loading image...
here is your image
Elapsed time: 0.5052 seconds

This allows checking whether the last measured time is greater
or less than some 'reference' value.

This is useful if you want to display messages if some operation
has taken too long in the past.
@janfreyberg
Copy link
Copy Markdown
Contributor Author

I should note that I already use a pattern like this in a GUI application I have;
so I was happy to see this project!

@gahjelle
Copy link
Copy Markdown
Collaborator

gahjelle commented Jan 13, 2020

@janfreyberg Thanks for the PR. This is an interesting use case.

A couple of suggestions:

  • The ._value can potentially be interesting in itself, so we can promote it to be a variable without an underscore
  • ._value is not really descriptive. How about something like .elapsed_time, .last_elapsed_time, or simply .last instead?
  • Timer is a dataclass, so we don't need to manually implement comparison operators. We can rather add (order=True) to the decorator and make sure ._value is the first instance variable. See https://realpython.com/python-data-classes/#comparing-cards for an example.

Thanks for the idea.

@gahjelle gahjelle self-assigned this Jan 13, 2020
@gahjelle
Copy link
Copy Markdown
Collaborator

Hmm ... reusing the dataclass comparison operators may not immediately give us comparisons to numbers. I'll do some more investigations.

@janfreyberg
Copy link
Copy Markdown
Contributor Author

janfreyberg commented Jan 13, 2020

Sounds good!

elapsed_time sounds good, but I think then it may also be worth re-setting it to nan when calling start again, to avoid confusion. What do you think? If it's called last_elapsed_time, that doesn't seem so important.

I didn't know about this dataclass feature. I initially tried to save some code by using the functools.total_ordering decorator on the class, but it doesn't actually work when the value is nan, as it assumes that if a > b is false and a == b is false then a < b has to be true, which is not the case for nan (all will be false).

@gahjelle
Copy link
Copy Markdown
Collaborator

gahjelle commented Jan 13, 2020

Hi, I had a look at how the comparison operators are implemented in data classes, and I don't think my suggestion will work, because they will only compare to other Timer objects, which is not what we want.

In the spirit of keeping this as simple as possible, I suggest the following:

  • Expose an attribute called .last which is initialized to math.nan and hidden from init and repr.

  • Update .last to be equal to the current elapsed_time when a timer ends. I don't think we can reset it to math.nan when a timer starts, because then your example above would not work 😊

  • While it's a minor time penalty, I might suggest just do a search and replace elapsed_time with self.last inside stop(). Keeps the code very simple.

  • Then we don't implement any comparison at all. Instead, the users can use the .last attribute explicitly. In your example, the relevant lines would then be:

    if image_display_timer.last > 0.2:
        _display_hold_message()
    

    This is both easier to implement and more explicit about what's happening

What do you think?

@janfreyberg
Copy link
Copy Markdown
Contributor Author

Sounds like a good plan to me; all very good points :)

I'll do that now.

- also removes comparators, as timer.last can be used
  for comparison
Copy link
Copy Markdown
Collaborator

@gahjelle gahjelle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great! I have two tiny comments on the test file. Otherwise, this should be ready to go.

I'll add a changelog (#15 ) and a list of contributors (#14 ) to the repo as well (hopefully tomorrow) so that you'll get proper credit. Thanks for your efforts!

Comment thread tests/test_codetiming.py Outdated
Comment thread tests/test_codetiming.py Outdated
@gahjelle gahjelle merged commit d618696 into realpython:master Jan 14, 2020
@gahjelle
Copy link
Copy Markdown
Collaborator

Brilliant, thank you so much!

I'll add the changelog and list of contributors later today, and then make a new release on PyPI.

@janfreyberg
Copy link
Copy Markdown
Contributor Author

Awesome, thank you 😃 will update my GUI projects as soon as the new release is out.

@gahjelle
Copy link
Copy Markdown
Collaborator

codetiming v1.1.0 has been released 🚀 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants