Experimental Discord bot written in Python
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

rscollections.py 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. from abc import ABC, abstractmethod
  2. """
  3. Subclasses of list, set, and dict with special behaviors.
  4. """
  5. # Abstract collections
  6. class AbstractMutableList(list, ABC):
  7. """
  8. Abstract list with hooks for custom logic before and after mutation
  9. operations.
  10. """
  11. @abstractmethod
  12. def pre_mutate(self):
  13. """
  14. Called just prior to any mutation operation.
  15. """
  16. raise NotImplementedError('subclass must implement pre_mutate')
  17. @abstractmethod
  18. def post_mutate(self):
  19. """
  20. Called just after any mutating operation before returning the result
  21. to the caller.
  22. """
  23. raise NotImplementedError('subclass must implement post_mutate')
  24. # Mutating method hooks
  25. def __delitem__(self, *args):
  26. self.pre_mutate()
  27. ret_val = super().__delitem__(*args)
  28. self.post_mutate()
  29. return ret_val
  30. def __iadd__(self, *args):
  31. self.pre_mutate()
  32. ret_val = super().__iadd__(*args)
  33. self.post_mutate()
  34. return ret_val
  35. def __imul__(self, *args):
  36. self.pre_mutate()
  37. ret_val = super().__imul__(*args)
  38. self.post_mutate()
  39. return ret_val
  40. def __setitem__(self, *args):
  41. self.pre_mutate()
  42. ret_val = super().__setitem__(*args)
  43. self.post_mutate()
  44. return ret_val
  45. def append(self, *args):
  46. self.pre_mutate()
  47. ret_val = super().append(*args)
  48. self.post_mutate()
  49. return ret_val
  50. def clear(self, *args):
  51. self.pre_mutate()
  52. ret_val = super().clear(*args)
  53. self.post_mutate()
  54. return ret_val
  55. def extend(self, *args):
  56. self.pre_mutate()
  57. ret_val = super().extend(*args)
  58. self.post_mutate()
  59. return ret_val
  60. def insert(self, *args):
  61. self.pre_mutate()
  62. ret_val = super().insert(*args)
  63. self.post_mutate()
  64. return ret_val
  65. def pop(self, *args):
  66. self.pre_mutate()
  67. ret_val = super().pop(*args)
  68. self.post_mutate()
  69. return ret_val
  70. def remove(self, *args):
  71. self.pre_mutate()
  72. ret_val = super().remove(*args)
  73. self.post_mutate()
  74. return ret_val
  75. def reverse(self, *args):
  76. self.pre_mutate()
  77. ret_val = super().reverse(*args)
  78. self.post_mutate()
  79. return ret_val
  80. def sort(self, *args):
  81. self.pre_mutate()
  82. ret_val = super().sort(*args)
  83. self.post_mutate()
  84. return ret_val
  85. class AbstractMutableSet(set, ABC):
  86. """
  87. Abstract set with hooks for custom logic before and after mutation
  88. operations.
  89. """
  90. @abstractmethod
  91. def pre_mutate(self):
  92. """
  93. Called just prior to any mutation operation.
  94. """
  95. raise NotImplementedError('subclass must implement pre_mutate')
  96. @abstractmethod
  97. def post_mutate(self):
  98. """
  99. Called just after any mutating operation before returning the result
  100. to the caller.
  101. """
  102. raise NotImplementedError('subclass must implement post_mutate')
  103. # Mutating method hooks
  104. def __iand__(self, *args):
  105. self.pre_mutate()
  106. ret_val = super().__iand__(*args)
  107. self.post_mutate()
  108. return ret_val
  109. def __ior__(self, *args):
  110. self.pre_mutate()
  111. ret_val = super().__ior__(*args)
  112. self.post_mutate()
  113. return ret_val
  114. def __isub__(self, *args):
  115. self.pre_mutate()
  116. ret_val = super().__isub__(*args)
  117. self.post_mutate()
  118. return ret_val
  119. def __ixor__(self, *args):
  120. self.pre_mutate()
  121. ret_val = super().__ixor__(*args)
  122. self.post_mutate()
  123. return ret_val
  124. def add(self, *args):
  125. self.pre_mutate()
  126. ret_val = super().add(*args)
  127. self.post_mutate()
  128. return ret_val
  129. def clear(self, *args):
  130. self.pre_mutate()
  131. ret_val = super().clear(*args)
  132. self.post_mutate()
  133. return ret_val
  134. def difference_update(self, *args):
  135. self.pre_mutate()
  136. ret_val = super().difference_update(*args)
  137. self.post_mutate()
  138. return ret_val
  139. def discard(self, *args):
  140. self.pre_mutate()
  141. ret_val = super().discard(*args)
  142. self.post_mutate()
  143. return ret_val
  144. def intersection_update(self, *args):
  145. self.pre_mutate()
  146. ret_val = super().intersection_update(*args)
  147. self.post_mutate()
  148. return ret_val
  149. def pop(self, *args):
  150. self.pre_mutate()
  151. ret_val = super().pop(*args)
  152. self.post_mutate()
  153. return ret_val
  154. def remove(self, *args):
  155. self.pre_mutate()
  156. ret_val = super().remove(*args)
  157. self.post_mutate()
  158. return ret_val
  159. def symmetric_difference_update(self, *args):
  160. self.pre_mutate()
  161. ret_val = super().symmetric_difference_update(*args)
  162. self.post_mutate()
  163. return ret_val
  164. def update(self, *args):
  165. self.pre_mutate()
  166. ret_val = super().update(*args)
  167. self.post_mutate()
  168. return ret_val
  169. class AbstractMutableDict(dict, ABC):
  170. """
  171. Abstract dict with hooks for custom logic before and after mutation
  172. operations.
  173. """
  174. @abstractmethod
  175. def pre_mutate(self):
  176. """
  177. Called just prior to any mutation operation.
  178. """
  179. raise NotImplementedError('subclass must implement pre_mutate')
  180. @abstractmethod
  181. def post_mutate(self):
  182. """
  183. Called just after any mutating operation before returning the result
  184. to the caller.
  185. """
  186. raise NotImplementedError('subclass must implement post_mutate')
  187. # Mutating method hooks
  188. def __delitem__(self, *args):
  189. self.pre_mutate()
  190. ret_val = super().__delitem__(*args)
  191. self.post_mutate()
  192. return ret_val
  193. def __ior__(self, *args):
  194. self.pre_mutate()
  195. ret_val = super().__ior__(*args)
  196. self.post_mutate()
  197. return ret_val
  198. def __setitem__(self, *args):
  199. self.pre_mutate()
  200. ret_val = super().__setitem__(*args)
  201. self.post_mutate()
  202. return ret_val
  203. def clear(self, *args):
  204. self.pre_mutate()
  205. ret_val = super().clear(*args)
  206. self.post_mutate()
  207. return ret_val
  208. def pop(self, *args):
  209. self.pre_mutate()
  210. ret_val = super().pop(*args)
  211. self.post_mutate()
  212. return ret_val
  213. def popitem(self, *args):
  214. self.pre_mutate()
  215. ret_val = super().popitem(*args)
  216. self.post_mutate()
  217. return ret_val
  218. def update(self, *args):
  219. self.pre_mutate()
  220. ret_val = super().update(*args)
  221. self.post_mutate()
  222. return ret_val
  223. # Collections with limited number of elements
  224. class SizeBoundList(AbstractMutableList):
  225. """
  226. Subclass of `list` that enforces a maximum number of elements.
  227. If the number of elements following a mutating operation exceeds
  228. `self.max_element_count`, then each element will be tested for its "age,"
  229. and the "oldest" elements will be removed until the total size is back
  230. within the limit. "Age" is determined via a provided lambda function. It is
  231. a value with arbitrary numeric value that is used comparitively to find
  232. the element with the smallest value.
  233. The `element_age` lambda takes two arguments: the element index and the
  234. element value. It must return values that can be compared to one another
  235. (e.g. int, float, datetime).
  236. `self.element_age` and `self.max_element_count` can be modified at runtime,
  237. however elements will only be discarded following the next mutating
  238. operation. Call `self.purge_old_elements()` to force resizing.
  239. """
  240. def __init__(self, max_element_count: int, element_age, *args, **kwargs):
  241. super().__init__(*args, **kwargs)
  242. self.element_age = element_age
  243. self.max_element_count = max_element_count
  244. self.is_culling = False
  245. self.post_mutate()
  246. def purge_old_elements(self):
  247. """
  248. Manually purges elements. Purging usually occurs automatically, but
  249. this can be called if `self.max_element_count` or `self.element_age`
  250. are modified at runtime.
  251. """
  252. self.post_mutate()
  253. def pre_mutate(self):
  254. pass
  255. def post_mutate(self):
  256. if self.is_culling:
  257. return
  258. self.is_culling = True
  259. while len(self) > self.max_element_count:
  260. oldest_age = None
  261. oldest_index = -1
  262. for i in range(len(self)):
  263. elem = self[i]
  264. age = self.element_age(i, elem)
  265. if oldest_age is None or age < oldest_age:
  266. oldest_age = age
  267. oldest_index = i
  268. self.pop(oldest_index)
  269. self.is_culling = False
  270. def copy():
  271. return SizeBoundList(max_element_count, element_age, super())
  272. class SizeBoundSet(AbstractMutableSet):
  273. """
  274. Subclass of `set` that enforces a maximum number of elements.
  275. If the number of elements following a mutating operation exceeds
  276. `self.max_element_count`, then each element will be tested for its "age,"
  277. and the "oldest" elements will be removed until the total size is back
  278. within the limit. "Age" is determined via a provided lambda function. It is
  279. a value with arbitrary numeric value that is used comparitively to find
  280. the element with the smallest value.
  281. The `element_age` lambda takes one argument: the element value. It must
  282. return values that can be compared to one another (e.g. int, float,
  283. datetime).
  284. `self.element_age` and `self.max_element_count` can be modified at runtime,
  285. however elements will only be discarded following the next mutating
  286. operation. Call `self.purge_old_elements()` to force resizing.
  287. """
  288. def __init__(self, max_element_count: int, element_age, *args, **kwargs):
  289. super().__init__(*args, **kwargs)
  290. self.element_age = element_age
  291. self.max_element_count = max_element_count
  292. self.is_culling = False
  293. self.post_mutate()
  294. def purge_old_elements(self):
  295. """
  296. Manually purges elements. Purging usually occurs automatically, but
  297. this can be called if `self.max_element_count` or `self.element_age`
  298. are modified at runtime.
  299. """
  300. self.post_mutate()
  301. def pre_mutate(self):
  302. pass
  303. def post_mutate(self):
  304. if self.is_culling:
  305. return
  306. self.is_culling = True
  307. while len(self) > self.max_element_count:
  308. oldest_age = None
  309. oldest_elem = None
  310. for elem in self:
  311. age = self.element_age(elem)
  312. if oldest_age is None or age < oldest_age:
  313. oldest_age = age
  314. oldest_elem = elem
  315. self.remove(oldest_elem)
  316. self.is_culling = False
  317. def copy():
  318. return SizeBoundSet(max_element_count, element_age, super())
  319. class SizeBoundDict(AbstractMutableDict):
  320. """
  321. Subclass of `dict` that enforces a maximum number of elements.
  322. If the number of elements following a mutating operation exceeds
  323. `self.max_element_count`, then each element will be tested for its "age,"
  324. and the "oldest" elements will be removed until the total size is back
  325. within the limit. "Age" is determined via a provided lambda function. It is
  326. a value with arbitrary numeric value that is used comparitively to find
  327. the element with the smallest value.
  328. The `element_age` lambda takes two arguments: the key and the value of a
  329. dict pair. It must return values that can be compared to one another (e.g.
  330. int, float, datetime).
  331. `self.element_age` and `self.max_element_count` can be modified at runtime,
  332. however elements will only be discarded following the next mutating
  333. operation. Call `self.purge_old_elements()` to force resizing.
  334. """
  335. def __init__(self, max_element_count = 10, element_age = (lambda key, value: float(key)), *args, **kwargs):
  336. super().__init__(*args, **kwargs)
  337. self.element_age = element_age
  338. self.max_element_count = max_element_count
  339. self.is_culling = False
  340. self.post_mutate()
  341. def purge_old_elements(self):
  342. """
  343. Manually purges elements. Purging usually occurs automatically, but
  344. this can be called if `self.max_element_count` or `self.element_age`
  345. are modified at runtime.
  346. """
  347. self.post_mutate()
  348. def pre_mutate(self):
  349. pass
  350. def post_mutate(self):
  351. if self.is_culling:
  352. return
  353. self.is_culling = True
  354. while len(self) > self.max_element_count:
  355. oldest_age = None
  356. oldest_key = None
  357. for key, value in self.items():
  358. age = self.element_age(key, value)
  359. if oldest_age is None or age < oldest_age:
  360. oldest_age = age
  361. oldest_key = key
  362. del self[oldest_key]
  363. self.is_culling = False
  364. def copy():
  365. return SizeBoundDict(max_element_count, element_age, super())
  366. # Collections with limited age of elements
  367. class AgeBoundList(AbstractMutableList):
  368. """
  369. Subclass of `list` that enforces a maximum "age" of elements.
  370. After each mutating operation, the minimum and maximum "age" of the elements
  371. are determined. If the span between the newest and oldest exceeds
  372. `self.max_age` then then the oldest elements will be purged until that is
  373. no longer the case. "Age" is determined via a provided lambda function. It
  374. is a value with arbitrary numeric value that is used comparitively.
  375. The `element_age` lambda takes two arguments: the element index and the
  376. element value. It must return values that can be compared to one another
  377. and be added and subtracted (e.g. int, float, datetime). If the lambda
  378. returns `datetime`s, the `max_age` should be a `timedelta`.
  379. `self.element_age` and `self.max_age` can be modified at runtime,
  380. however elements will only be discarded following the next mutating
  381. operation. Call `self.purge_old_elements()` to force resizing.
  382. """
  383. def __init__(self, max_age, element_age, *args, **kwargs):
  384. super().__init__(*args, **kwargs)
  385. self.max_age = max_age
  386. self.element_age = element_age
  387. self.is_culling = False
  388. def purge_old_elements(self):
  389. """
  390. Manually purges elements. Purging usually occurs automatically, but
  391. this can be called if `self.max_age` or `self.element_age` are modified
  392. at runtime.
  393. """
  394. self.post_mutate()
  395. def pre_mutate(self):
  396. pass
  397. def post_mutate(self):
  398. if self.is_culling or len(self) <= 1:
  399. return
  400. self.is_culling = True
  401. min_age = None
  402. max_age = None
  403. ages = {}
  404. for i in range(len(self)):
  405. elem = self[i]
  406. age = self.element_age(i, elem)
  407. ages[i] = age
  408. if min_age is None or age < min_age:
  409. min_age = age
  410. if max_age is None or age > max_age:
  411. max_age = age
  412. cutoff = max_age - self.max_age
  413. if min_age >= cutoff:
  414. self.is_culling = False
  415. return
  416. for i in reversed(range(len(self))):
  417. if ages[i] < cutoff:
  418. del self[i]
  419. self.is_culling = False
  420. class AgeBoundSet(AbstractMutableSet):
  421. """
  422. Subclass of `set` that enforces a maximum "age" of elements.
  423. After each mutating operation, the minimum and maximum "age" of the elements
  424. are determined. If the span between the newest and oldest exceeds
  425. `self.max_age` then then the oldest elements will be purged until that is
  426. no longer the case. "Age" is determined via a provided lambda function. It
  427. is a value with arbitrary numeric value that is used comparitively.
  428. The `element_age` lambda takes one argument: the element value. It must
  429. return values that can be compared to one another and be added and
  430. subtracted (e.g. int, float, datetime). If the lambda returns `datetime`s,
  431. the `max_age` should be a `timedelta`.
  432. `self.element_age` and `self.max_age` can be modified at runtime,
  433. however elements will only be discarded following the next mutating
  434. operation. Call `self.purge_old_elements()` to force resizing.
  435. """
  436. def __init__(self, max_age, element_age, *args, **kwargs):
  437. super().__init__(*args, **kwargs)
  438. self.max_age = max_age
  439. self.element_age = element_age
  440. self.is_culling = False
  441. def purge_old_elements(self):
  442. """
  443. Manually purges elements. Purging usually occurs automatically, but
  444. this can be called if `self.max_age` or `self.element_age` are modified
  445. at runtime.
  446. """
  447. self.post_mutate()
  448. def pre_mutate(self):
  449. pass
  450. def post_mutate(self):
  451. if self.is_culling or len(self) <= 1:
  452. return
  453. self.is_culling = True
  454. min_age = None
  455. max_age = None
  456. ages = {}
  457. for elem in self:
  458. age = self.element_age(elem)
  459. ages[elem] = age
  460. if min_age is None or age < min_age:
  461. min_age = age
  462. if max_age is None or age > max_age:
  463. max_age = age
  464. cutoff = max_age - self.max_age
  465. if min_age >= cutoff:
  466. self.is_culling = False
  467. return
  468. for elem, age in ages.items():
  469. if age < cutoff:
  470. self.remove(elem)
  471. self.is_culling = False
  472. class AgeBoundDict(AbstractMutableDict):
  473. """
  474. Subclass of `dict` that enforces a maximum "age" of elements.
  475. After each mutating operation, the minimum and maximum "age" of the elements
  476. are determined. If the span between the newest and oldest exceeds
  477. `self.max_age` then then the oldest elements will be purged until that is
  478. no longer the case. "Age" is determined via a provided lambda function. It
  479. is a value with arbitrary numeric value that is used comparitively.
  480. The `element_age` lambda takes two arguments: the key and value of a pair.
  481. It must return values that can be compared to one another and be added and
  482. subtracted (e.g. int, float, datetime). If the lambda returns `datetime`s,
  483. the `max_age` should be a `timedelta`.
  484. `self.element_age` and `self.max_age` can be modified at runtime,
  485. however elements will only be discarded following the next mutating
  486. operation. Call `self.purge_old_elements()` to force resizing.
  487. """
  488. def __init__(self, max_age, element_age, *args, **kwargs):
  489. super().__init__(*args, **kwargs)
  490. self.max_age = max_age
  491. self.element_age = element_age
  492. self.is_culling = False
  493. def purge_old_elements(self):
  494. """
  495. Manually purges elements. Purging usually occurs automatically, but
  496. this can be called if `self.max_age` or `self.element_age` are modified
  497. at runtime.
  498. """
  499. self.post_mutate()
  500. def pre_mutate(self):
  501. pass
  502. def post_mutate(self):
  503. if self.is_culling or len(self) <= 1:
  504. return
  505. self.is_culling = True
  506. min_age = None
  507. max_age = None
  508. ages = {}
  509. for key, value in self.items():
  510. age = self.element_age(key, value)
  511. ages[key] = age
  512. if min_age is None or age < min_age:
  513. min_age = age
  514. if max_age is None or age > max_age:
  515. max_age = age
  516. cutoff = max_age - self.max_age
  517. if min_age >= cutoff:
  518. self.is_culling = False
  519. return
  520. for key, age in ages.items():
  521. if age < cutoff:
  522. del self[key]
  523. self.is_culling = False