1
0

calcex.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import copy
  2. try:
  3. import yaml
  4. import yaml.nodes
  5. except ImportError:
  6. yaml = None
  7. class UnitMismatchError(ValueError):
  8. pass
  9. class Figure(object):
  10. yaml_tag = u"!figure"
  11. def __init__(self, value=None, unit=None):
  12. self.value = value
  13. self.unit = unit
  14. def get_value(self):
  15. return self._value
  16. def set_value(self, value):
  17. self._value = copy.deepcopy(value)
  18. """ use property() instead of decorator to enable overriding """
  19. value = property(get_value, set_value)
  20. def get_unit(self):
  21. return self._unit
  22. def set_unit(self, unit):
  23. self._unit = copy.deepcopy(unit)
  24. """ use property() instead of decorator to enable overriding """
  25. unit = property(get_unit, set_unit)
  26. def __repr__(self):
  27. return '{}(value = {!r}, unit = {})'.format(type(self).__name__, self.value, self.unit)
  28. def __str__(self):
  29. if self.value is None and self.unit is None:
  30. return '?'
  31. elif self.unit is None:
  32. return '{}'.format(self.value)
  33. elif self.value is None:
  34. return '? {}'.format(self.unit)
  35. else:
  36. return '{} {}'.format(self.value, self.unit)
  37. @classmethod
  38. def from_yaml(cls, loader, node):
  39. if isinstance(node, yaml.nodes.ScalarNode):
  40. seg = loader.construct_scalar(node).split(' ')
  41. if seg[0] == '?':
  42. value = None
  43. else:
  44. try:
  45. value = int(seg[0])
  46. except ValueError:
  47. value = float(seg[0])
  48. return cls(
  49. value=value,
  50. unit=' '.join(seg[1:]) if len(seg) > 1 else None,
  51. )
  52. else:
  53. return cls(**loader.construct_mapping(node, deep=True))
  54. @classmethod
  55. def register_yaml_constructor(cls, loader, tag=None):
  56. loader.add_constructor(
  57. cls.yaml_tag if tag is None else tag,
  58. cls.from_yaml,
  59. )
  60. @classmethod
  61. def to_yaml(cls, dumper, figure, tag=None):
  62. tag = tag or cls.yaml_tag
  63. if figure.value is None or type(figure.value) in [int, float]:
  64. if figure.value is None:
  65. value_text = u'?'
  66. else:
  67. value_text = u'{}'.format(figure.value)
  68. if figure.unit is None:
  69. figure_text = value_text
  70. else:
  71. figure_text = u'{} {}'.format(value_text, figure.unit)
  72. return dumper.represent_scalar(tag=tag, value=figure_text)
  73. else:
  74. return dumper.represent_mapping(
  75. tag=tag,
  76. mapping={'value': figure.value, 'unit': figure.unit},
  77. )
  78. @classmethod
  79. def register_yaml_representer(cls, dumper, tag=None):
  80. tag = tag or cls.yaml_tag
  81. dumper.add_representer(cls, lambda d, f: cls.to_yaml(d, f, tag=tag))
  82. def __eq__(self, other):
  83. return isinstance(self, type(other)) and vars(self) == vars(other)
  84. def __ne__(self, other):
  85. return not (self == other)
  86. def __add__(self, other):
  87. if not isinstance(self, type(other)):
  88. raise NotImplementedError('{!r} + {!r}'.format(self, other))
  89. assert not self.value is None
  90. assert not other.value is None
  91. if self.unit != other.unit:
  92. raise UnitMismatchError('{} + {}'.format(self, other))
  93. else:
  94. return type(self)(value=self.value + other.value, unit=self.unit)
  95. def __radd__(self, other):
  96. """ enables use of sum() """
  97. if isinstance(other, int) and other == 0:
  98. return copy.deepcopy(self)
  99. else:
  100. raise NotImplementedError('{!r} + {!r}'.format(other, self))
  101. def __sub__(self, other):
  102. if not isinstance(self, type(other)):
  103. raise NotImplementedError('{!r} - {!r}'.format(self, other))
  104. assert not self.value is None
  105. assert not other.value is None
  106. if self.unit != other.unit:
  107. raise UnitMismatchError('{} - {}'.format(self, other))
  108. else:
  109. return type(self)(value=self.value - other.value, unit=self.unit)
  110. def __mul__(self, factor):
  111. if isinstance(factor, Figure):
  112. assert not self.value is None
  113. assert not factor.value is None
  114. assert factor.unit is None
  115. return type(self)(value=self.value * factor.value, unit=self.unit)
  116. else:
  117. return self * Figure(value=factor, unit=None)
  118. """
  119. $ python2
  120. >>> 3/2
  121. 1
  122. $ python3
  123. >>> 3/2
  124. 1.5
  125. >>> 4/2
  126. 2.0
  127. """
  128. def __truediv__(self, divisor):
  129. if isinstance(divisor, Figure):
  130. assert not self.value is None
  131. assert not divisor.value is None
  132. if isinstance(self.value, int):
  133. value = float(self.value) / divisor.value
  134. else:
  135. value = self.value / divisor.value
  136. if self.unit == divisor.unit:
  137. return Figure(value=value, unit=None)
  138. elif divisor.unit is None:
  139. return type(self)(value=value, unit=self.unit)
  140. else:
  141. raise NotImplementedError('{!r} / {!r}'.format(self, divisor))
  142. else:
  143. return self / Figure(value=divisor, unit=None)
  144. def __div__(self, divisor):
  145. return self.__truediv__(divisor)
  146. def __round__(self, *params, **kwargs):
  147. return type(self)(
  148. value=round(self.value, *params, **kwargs),
  149. unit=self.unit,
  150. )