Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

_constants.py 16KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. # -*- test-case-name: constantly.test.test_constants -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. Symbolic constant support, including collections and constants with text,
  6. numeric, and bit flag values.
  7. """
  8. from __future__ import division, absolute_import
  9. __all__ = [
  10. 'NamedConstant', 'ValueConstant', 'FlagConstant',
  11. 'Names', 'Values', 'Flags']
  12. from functools import partial
  13. from itertools import count
  14. from operator import and_, or_, xor
  15. _unspecified = object()
  16. _constantOrder = partial(next, count())
  17. class _Constant(object):
  18. """
  19. @ivar _index: A C{int} allocated from a shared counter in order to keep
  20. track of the order in which L{_Constant}s are instantiated.
  21. @ivar name: A C{str} giving the name of this constant; only set once the
  22. constant is initialized by L{_ConstantsContainer}.
  23. @ivar _container: The L{_ConstantsContainer} subclass this constant belongs
  24. to; C{None} until the constant is initialized by that subclass.
  25. """
  26. def __init__(self):
  27. self._container = None
  28. self._index = _constantOrder()
  29. def __repr__(self):
  30. """
  31. Return text identifying both which constant this is and which
  32. collection it belongs to.
  33. """
  34. return "<%s=%s>" % (self._container.__name__, self.name)
  35. def __lt__(self, other):
  36. """
  37. Implements C{<}. Order is defined by instantiation order.
  38. @param other: An object.
  39. @return: C{NotImplemented} if C{other} is not a constant belonging to
  40. the same container as this constant, C{True} if this constant is
  41. defined before C{other}, otherwise C{False}.
  42. """
  43. if (
  44. not isinstance(other, self.__class__) or
  45. not self._container == other._container
  46. ):
  47. return NotImplemented
  48. return self._index < other._index
  49. def __le__(self, other):
  50. """
  51. Implements C{<=}. Order is defined by instantiation order.
  52. @param other: An object.
  53. @return: C{NotImplemented} if C{other} is not a constant belonging to
  54. the same container as this constant, C{True} if this constant is
  55. defined before or equal to C{other}, otherwise C{False}.
  56. """
  57. if (
  58. not isinstance(other, self.__class__) or
  59. not self._container == other._container
  60. ):
  61. return NotImplemented
  62. return self is other or self._index < other._index
  63. def __gt__(self, other):
  64. """
  65. Implements C{>}. Order is defined by instantiation order.
  66. @param other: An object.
  67. @return: C{NotImplemented} if C{other} is not a constant belonging to
  68. the same container as this constant, C{True} if this constant is
  69. defined after C{other}, otherwise C{False}.
  70. """
  71. if (
  72. not isinstance(other, self.__class__) or
  73. not self._container == other._container
  74. ):
  75. return NotImplemented
  76. return self._index > other._index
  77. def __ge__(self, other):
  78. """
  79. Implements C{>=}. Order is defined by instantiation order.
  80. @param other: An object.
  81. @return: C{NotImplemented} if C{other} is not a constant belonging to
  82. the same container as this constant, C{True} if this constant is
  83. defined after or equal to C{other}, otherwise C{False}.
  84. """
  85. if (
  86. not isinstance(other, self.__class__) or
  87. not self._container == other._container
  88. ):
  89. return NotImplemented
  90. return self is other or self._index > other._index
  91. def _realize(self, container, name, value):
  92. """
  93. Complete the initialization of this L{_Constant}.
  94. @param container: The L{_ConstantsContainer} subclass this constant is
  95. part of.
  96. @param name: The name of this constant in its container.
  97. @param value: The value of this constant; not used, as named constants
  98. have no value apart from their identity.
  99. """
  100. self._container = container
  101. self.name = name
  102. class _ConstantsContainerType(type):
  103. """
  104. L{_ConstantsContainerType} is a metaclass for creating constants container
  105. classes.
  106. """
  107. def __new__(self, name, bases, attributes):
  108. """
  109. Create a new constants container class.
  110. If C{attributes} includes a value of C{None} for the C{"_constantType"}
  111. key, the new class will not be initialized as a constants container and
  112. it will behave as a normal class.
  113. @param name: The name of the container class.
  114. @type name: L{str}
  115. @param bases: A tuple of the base classes for the new container class.
  116. @type bases: L{tuple} of L{_ConstantsContainerType} instances
  117. @param attributes: The attributes of the new container class, including
  118. any constants it is to contain.
  119. @type attributes: L{dict}
  120. """
  121. cls = super(_ConstantsContainerType, self).__new__(
  122. self, name, bases, attributes)
  123. # Only realize constants in concrete _ConstantsContainer subclasses.
  124. # Ignore intermediate base classes.
  125. constantType = getattr(cls, '_constantType', None)
  126. if constantType is None:
  127. return cls
  128. constants = []
  129. for (name, descriptor) in attributes.items():
  130. if isinstance(descriptor, cls._constantType):
  131. if descriptor._container is not None:
  132. raise ValueError(
  133. "Cannot use %s as the value of an attribute on %s" % (
  134. descriptor, cls.__name__))
  135. constants.append((descriptor._index, name, descriptor))
  136. enumerants = {}
  137. for (index, enumerant, descriptor) in sorted(constants):
  138. value = cls._constantFactory(enumerant, descriptor)
  139. descriptor._realize(cls, enumerant, value)
  140. enumerants[enumerant] = descriptor
  141. # Save the dictionary which contains *only* constants (distinct from
  142. # any other attributes the application may have given the container)
  143. # where the class can use it later (eg for lookupByName).
  144. cls._enumerants = enumerants
  145. return cls
  146. # In Python3 metaclasses are defined using a C{metaclass} keyword argument in
  147. # the class definition. This would cause a syntax error in Python2.
  148. # So we use L{type} to introduce an intermediate base class with the desired
  149. # metaclass.
  150. # See:
  151. # * http://docs.python.org/2/library/functions.html#type
  152. # * http://docs.python.org/3/reference/datamodel.html#customizing-class-creation
  153. class _ConstantsContainer(_ConstantsContainerType('', (object,), {})):
  154. """
  155. L{_ConstantsContainer} is a class with attributes used as symbolic
  156. constants. It is up to subclasses to specify what kind of constants are
  157. allowed.
  158. @cvar _constantType: Specified by a L{_ConstantsContainer} subclass to
  159. specify the type of constants allowed by that subclass.
  160. @cvar _enumerants: A C{dict} mapping the names of constants (eg
  161. L{NamedConstant} instances) found in the class definition to those
  162. instances.
  163. """
  164. _constantType = None
  165. def __new__(cls):
  166. """
  167. Classes representing constants containers are not intended to be
  168. instantiated.
  169. The class object itself is used directly.
  170. """
  171. raise TypeError("%s may not be instantiated." % (cls.__name__,))
  172. @classmethod
  173. def _constantFactory(cls, name, descriptor):
  174. """
  175. Construct the value for a new constant to add to this container.
  176. @param name: The name of the constant to create.
  177. @param descriptor: An instance of a L{_Constant} subclass (eg
  178. L{NamedConstant}) which is assigned to C{name}.
  179. @return: L{NamedConstant} instances have no value apart from identity,
  180. so return a meaningless dummy value.
  181. """
  182. return _unspecified
  183. @classmethod
  184. def lookupByName(cls, name):
  185. """
  186. Retrieve a constant by its name or raise a C{ValueError} if there is no
  187. constant associated with that name.
  188. @param name: A C{str} giving the name of one of the constants defined
  189. by C{cls}.
  190. @raise ValueError: If C{name} is not the name of one of the constants
  191. defined by C{cls}.
  192. @return: The L{NamedConstant} associated with C{name}.
  193. """
  194. if name in cls._enumerants:
  195. return getattr(cls, name)
  196. raise ValueError(name)
  197. @classmethod
  198. def iterconstants(cls):
  199. """
  200. Iteration over a L{Names} subclass results in all of the constants it
  201. contains.
  202. @return: an iterator the elements of which are the L{NamedConstant}
  203. instances defined in the body of this L{Names} subclass.
  204. """
  205. constants = cls._enumerants.values()
  206. return iter(
  207. sorted(constants, key=lambda descriptor: descriptor._index))
  208. class NamedConstant(_Constant):
  209. """
  210. L{NamedConstant} defines an attribute to be a named constant within a
  211. collection defined by a L{Names} subclass.
  212. L{NamedConstant} is only for use in the definition of L{Names}
  213. subclasses. Do not instantiate L{NamedConstant} elsewhere and do not
  214. subclass it.
  215. """
  216. class Names(_ConstantsContainer):
  217. """
  218. A L{Names} subclass contains constants which differ only in their names and
  219. identities.
  220. """
  221. _constantType = NamedConstant
  222. class ValueConstant(_Constant):
  223. """
  224. L{ValueConstant} defines an attribute to be a named constant within a
  225. collection defined by a L{Values} subclass.
  226. L{ValueConstant} is only for use in the definition of L{Values} subclasses.
  227. Do not instantiate L{ValueConstant} elsewhere and do not subclass it.
  228. """
  229. def __init__(self, value):
  230. _Constant.__init__(self)
  231. self.value = value
  232. class Values(_ConstantsContainer):
  233. """
  234. A L{Values} subclass contains constants which are associated with arbitrary
  235. values.
  236. """
  237. _constantType = ValueConstant
  238. @classmethod
  239. def lookupByValue(cls, value):
  240. """
  241. Retrieve a constant by its value or raise a C{ValueError} if there is
  242. no constant associated with that value.
  243. @param value: The value of one of the constants defined by C{cls}.
  244. @raise ValueError: If C{value} is not the value of one of the constants
  245. defined by C{cls}.
  246. @return: The L{ValueConstant} associated with C{value}.
  247. """
  248. for constant in cls.iterconstants():
  249. if constant.value == value:
  250. return constant
  251. raise ValueError(value)
  252. def _flagOp(op, left, right):
  253. """
  254. Implement a binary operator for a L{FlagConstant} instance.
  255. @param op: A two-argument callable implementing the binary operation. For
  256. example, C{operator.or_}.
  257. @param left: The left-hand L{FlagConstant} instance.
  258. @param right: The right-hand L{FlagConstant} instance.
  259. @return: A new L{FlagConstant} instance representing the result of the
  260. operation.
  261. """
  262. value = op(left.value, right.value)
  263. names = op(left.names, right.names)
  264. result = FlagConstant()
  265. result._realize(left._container, names, value)
  266. return result
  267. class FlagConstant(_Constant):
  268. """
  269. L{FlagConstant} defines an attribute to be a flag constant within a
  270. collection defined by a L{Flags} subclass.
  271. L{FlagConstant} is only for use in the definition of L{Flags} subclasses.
  272. Do not instantiate L{FlagConstant} elsewhere and do not subclass it.
  273. """
  274. def __init__(self, value=_unspecified):
  275. _Constant.__init__(self)
  276. self.value = value
  277. def _realize(self, container, names, value):
  278. """
  279. Complete the initialization of this L{FlagConstant}.
  280. This implementation differs from other C{_realize} implementations in
  281. that a L{FlagConstant} may have several names which apply to it, due to
  282. flags being combined with various operators.
  283. @param container: The L{Flags} subclass this constant is part of.
  284. @param names: When a single-flag value is being initialized, a C{str}
  285. giving the name of that flag. This is the case which happens when
  286. a L{Flags} subclass is being initialized and L{FlagConstant}
  287. instances from its body are being realized. Otherwise, a C{set} of
  288. C{str} giving names of all the flags set on this L{FlagConstant}
  289. instance. This is the case when two flags are combined using C{|},
  290. for example.
  291. """
  292. if isinstance(names, str):
  293. name = names
  294. names = set([names])
  295. elif len(names) == 1:
  296. (name,) = names
  297. else:
  298. name = "{" + ",".join(sorted(names)) + "}"
  299. _Constant._realize(self, container, name, value)
  300. self.value = value
  301. self.names = names
  302. def __or__(self, other):
  303. """
  304. Define C{|} on two L{FlagConstant} instances to create a new
  305. L{FlagConstant} instance with all flags set in either instance set.
  306. """
  307. return _flagOp(or_, self, other)
  308. def __and__(self, other):
  309. """
  310. Define C{&} on two L{FlagConstant} instances to create a new
  311. L{FlagConstant} instance with only flags set in both instances set.
  312. """
  313. return _flagOp(and_, self, other)
  314. def __xor__(self, other):
  315. """
  316. Define C{^} on two L{FlagConstant} instances to create a new
  317. L{FlagConstant} instance with only flags set on exactly one instance
  318. set.
  319. """
  320. return _flagOp(xor, self, other)
  321. def __invert__(self):
  322. """
  323. Define C{~} on a L{FlagConstant} instance to create a new
  324. L{FlagConstant} instance with all flags not set on this instance set.
  325. """
  326. result = FlagConstant()
  327. result._realize(self._container, set(), 0)
  328. for flag in self._container.iterconstants():
  329. if flag.value & self.value == 0:
  330. result |= flag
  331. return result
  332. def __iter__(self):
  333. """
  334. @return: An iterator of flags set on this instance set.
  335. """
  336. return (self._container.lookupByName(name) for name in self.names)
  337. def __contains__(self, flag):
  338. """
  339. @param flag: The flag to test for membership in this instance
  340. set.
  341. @return: C{True} if C{flag} is in this instance set, else
  342. C{False}.
  343. """
  344. # Optimization for testing membership without iteration.
  345. return bool(flag & self)
  346. def __nonzero__(self):
  347. """
  348. @return: C{False} if this flag's value is 0, else C{True}.
  349. """
  350. return bool(self.value)
  351. __bool__ = __nonzero__
  352. class Flags(Values):
  353. """
  354. A L{Flags} subclass contains constants which can be combined using the
  355. common bitwise operators (C{|}, C{&}, etc) similar to a I{bitvector} from a
  356. language like C.
  357. """
  358. _constantType = FlagConstant
  359. _value = 1
  360. @classmethod
  361. def _constantFactory(cls, name, descriptor):
  362. """
  363. For L{FlagConstant} instances with no explicitly defined value, assign
  364. the next power of two as its value.
  365. @param name: The name of the constant to create.
  366. @param descriptor: An instance of a L{FlagConstant} which is assigned
  367. to C{name}.
  368. @return: Either the value passed to the C{descriptor} constructor, or
  369. the next power of 2 value which will be assigned to C{descriptor},
  370. relative to the value of the last defined L{FlagConstant}.
  371. """
  372. if descriptor.value is _unspecified:
  373. value = cls._value
  374. cls._value <<= 1
  375. else:
  376. value = descriptor.value
  377. cls._value = value << 1
  378. return value