Experimental Discord bot written in Python
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

collections.py 17KB

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