core.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import time
  2. from contextlib import contextmanager
  3. from .human_count import HumanCount
  4. from .human_duration import HumanDuration
  5. from .human_throughput import HumanThroughput
  6. def about_time(func_or_it=None, *args, **kwargs):
  7. """Measure timing and throughput of code blocks, with beautiful
  8. human friendly representations.
  9. There are three modes of operation: context manager, callable and
  10. throughput.
  11. 1. Use it like a context manager:
  12. >>> with about_time() as t:
  13. .... # code block.
  14. 2. Use it with a callable:
  15. >>> def func(a, b): ...
  16. >>> t = about_time(func, 1, b=2) # send arguments at will.
  17. 3. Use it with an iterable or generator:
  18. >>> t = about_time(it) # any iterable or generator.
  19. >>> for item in t:
  20. .... # use item
  21. """
  22. timings = [0.0, 0.0]
  23. # use as a context manager.
  24. if func_or_it is None:
  25. return _context_timing(timings, Handle(timings))
  26. # use as a callable.
  27. if callable(func_or_it):
  28. with _context_timing(timings):
  29. result = func_or_it(*args, **kwargs)
  30. return HandleResult(timings, result)
  31. try:
  32. it = iter(func_or_it)
  33. except TypeError:
  34. raise UserWarning('param should be callable or iterable.')
  35. # use as a counter/throughput iterator.
  36. def it_closure():
  37. with _context_timing(timings):
  38. for it_closure.count, elem in enumerate(it, 1): # iterators are iterable.
  39. yield elem
  40. it_closure.count = 0 # the count will only be updated after starting iterating.
  41. return HandleStats(timings, it_closure)
  42. @contextmanager
  43. def _context_timing(timings, handle=None):
  44. timings[0] = time.perf_counter()
  45. yield handle
  46. timings[1] = time.perf_counter()
  47. class Handle(object):
  48. def __init__(self, timings):
  49. self.__timings = timings
  50. @property
  51. def duration(self) -> float:
  52. """Return the actual duration in seconds.
  53. This is dynamically updated in real time.
  54. Returns:
  55. the number of seconds.
  56. """
  57. return (self.__timings[1] or time.perf_counter()) - self.__timings[0]
  58. @property
  59. def duration_human(self) -> HumanDuration:
  60. """Return a beautiful representation of the duration.
  61. It dynamically calculates the best unit to use.
  62. Returns:
  63. the human representation.
  64. """
  65. return HumanDuration(self.duration)
  66. class HandleResult(Handle):
  67. def __init__(self, timings, result):
  68. super(HandleResult, self).__init__(timings)
  69. self.__result = result
  70. @property
  71. def result(self):
  72. """Return the result of the callable.
  73. Returns:
  74. the result of the callable.
  75. """
  76. return self.__result
  77. class HandleStats(Handle):
  78. def __init__(self, timings, it_closure):
  79. super(HandleStats, self).__init__(timings)
  80. self.__it = it_closure
  81. def __iter__(self):
  82. return self.__it()
  83. @property
  84. def count(self) -> int:
  85. """Return the current iteration count.
  86. This is dynamically updated in real time.
  87. Returns:
  88. the current iteration count.
  89. """
  90. return self.__it.count
  91. @property
  92. def count_human(self) -> HumanCount:
  93. """Return a beautiful representation of the current iteration count.
  94. This is dynamically updated in real time.
  95. Returns:
  96. the human representation.
  97. """
  98. return self.count_human_as('')
  99. def count_human_as(self, unit: str) -> HumanCount:
  100. """Return a beautiful representation of the current iteration count.
  101. This is dynamically updated in real time.
  102. Args:
  103. unit: what is being measured
  104. Returns:
  105. the human representation.
  106. """
  107. return HumanCount(self.count, unit)
  108. @property
  109. def throughput(self) -> float:
  110. """Return the current throughput in items per second.
  111. This is dynamically updated in real time.
  112. Returns:
  113. the number of items per second.
  114. """
  115. try:
  116. return self.count / self.duration
  117. except ZeroDivisionError: # pragma: no cover
  118. return float('nan')
  119. @property
  120. def throughput_human(self) -> HumanThroughput:
  121. """Return a beautiful representation of the current throughput.
  122. It dynamically calculates the best unit to use.
  123. Returns:
  124. the human representation.
  125. """
  126. return self.throughput_human_as('')
  127. def throughput_human_as(self, unit: str) -> HumanThroughput:
  128. """Return a beautiful representation of the current throughput.
  129. It dynamically calculates the best unit to use.
  130. Args:
  131. unit: what is being measured
  132. Returns:
  133. the human representation.
  134. """
  135. return HumanThroughput(self.throughput, unit)