Experimental Discord bot written in Python
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

rscollections.py 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. """
  2. Subclasses of list, set, and dict with special behaviors.
  3. """
  4. from abc import ABC, abstractmethod
  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,
  241. max_element_count: int,
  242. element_age,
  243. *args, **kwargs):
  244. super().__init__(*args, **kwargs)
  245. self.element_age = element_age
  246. self.max_element_count = max_element_count
  247. self.is_culling = False
  248. self.post_mutate()
  249. def purge_old_elements(self):
  250. """
  251. Manually purges elements. Purging usually occurs automatically, but
  252. this can be called if `self.max_element_count` or `self.element_age`
  253. are modified at runtime.
  254. """
  255. self.post_mutate()
  256. def pre_mutate(self):
  257. pass
  258. def post_mutate(self):
  259. if self.is_culling:
  260. return
  261. self.is_culling = True
  262. while len(self) > self.max_element_count:
  263. oldest_age = None
  264. oldest_index = -1
  265. for i, elem in enumerate(self):
  266. age = self.element_age(i, elem)
  267. if oldest_age is None or age < oldest_age:
  268. oldest_age = age
  269. oldest_index = i
  270. self.pop(oldest_index)
  271. self.is_culling = False
  272. def copy(self):
  273. return SizeBoundList(self.max_element_count, self.element_age, super())
  274. class SizeBoundSet(AbstractMutableSet):
  275. """
  276. Subclass of `set` that enforces a maximum number of elements.
  277. If the number of elements following a mutating operation exceeds
  278. `self.max_element_count`, then each element will be tested for its "age,"
  279. and the "oldest" elements will be removed until the total size is back
  280. within the limit. "Age" is determined via a provided lambda function. It is
  281. a value with arbitrary numeric value that is used comparitively to find
  282. the element with the smallest value.
  283. The `element_age` lambda takes one argument: the element value. It must
  284. return values that can be compared to one another (e.g. int, float,
  285. datetime).
  286. `self.element_age` and `self.max_element_count` can be modified at runtime,
  287. however elements will only be discarded following the next mutating
  288. operation. Call `self.purge_old_elements()` to force resizing.
  289. """
  290. def __init__(self,
  291. max_element_count: int,
  292. element_age,
  293. *args, **kwargs):
  294. super().__init__(*args, **kwargs)
  295. self.element_age = element_age
  296. self.max_element_count = max_element_count
  297. self.is_culling = False
  298. self.post_mutate()
  299. def purge_old_elements(self):
  300. """
  301. Manually purges elements. Purging usually occurs automatically, but
  302. this can be called if `self.max_element_count` or `self.element_age`
  303. are modified at runtime.
  304. """
  305. self.post_mutate()
  306. def pre_mutate(self):
  307. pass
  308. def post_mutate(self):
  309. if self.is_culling:
  310. return
  311. self.is_culling = True
  312. while len(self) > self.max_element_count:
  313. oldest_age = None
  314. oldest_elem = None
  315. for elem in self:
  316. age = self.element_age(elem)
  317. if oldest_age is None or age < oldest_age:
  318. oldest_age = age
  319. oldest_elem = elem
  320. self.remove(oldest_elem)
  321. self.is_culling = False
  322. def copy(self):
  323. return SizeBoundSet(self.max_element_count, self.element_age, super())
  324. class SizeBoundDict(AbstractMutableDict):
  325. """
  326. Subclass of `dict` that enforces a maximum number of elements.
  327. If the number of elements following a mutating operation exceeds
  328. `self.max_element_count`, then each element will be tested for its "age,"
  329. and the "oldest" elements will be removed until the total size is back
  330. within the limit. "Age" is determined via a provided lambda function. It is
  331. a value with arbitrary numeric value that is used comparitively to find
  332. the element with the smallest value.
  333. The `element_age` lambda takes two arguments: the key and the value of a
  334. dict pair. It must return values that can be compared to one another (e.g.
  335. int, float, datetime).
  336. `self.element_age` and `self.max_element_count` can be modified at runtime,
  337. however elements will only be discarded following the next mutating
  338. operation. Call `self.purge_old_elements()` to force resizing.
  339. """
  340. def __init__(self,
  341. max_element_count: int,
  342. element_age,
  343. *args, **kwargs):
  344. super().__init__(*args, **kwargs)
  345. self.element_age = element_age
  346. self.max_element_count = max_element_count
  347. self.is_culling = False
  348. self.post_mutate()
  349. def purge_old_elements(self):
  350. """
  351. Manually purges elements. Purging usually occurs automatically, but
  352. this can be called if `self.max_element_count` or `self.element_age`
  353. are modified at runtime.
  354. """
  355. self.post_mutate()
  356. def pre_mutate(self):
  357. pass
  358. def post_mutate(self):
  359. if self.is_culling:
  360. return
  361. self.is_culling = True
  362. while len(self) > self.max_element_count:
  363. oldest_age = None
  364. oldest_key = None
  365. for key, value in self.items():
  366. age = self.element_age(key, value)
  367. if oldest_age is None or age < oldest_age:
  368. oldest_age = age
  369. oldest_key = key
  370. del self[oldest_key]
  371. self.is_culling = False
  372. def copy(self):
  373. return SizeBoundDict(self.max_element_count, self.element_age, super())
  374. # Collections with limited age of elements
  375. class AgeBoundList(AbstractMutableList):
  376. """
  377. Subclass of `list` that enforces a maximum "age" of elements.
  378. After each mutating operation, the minimum and maximum "age" of the elements
  379. are determined. If the span between the newest and oldest exceeds
  380. `self.max_age` then then the oldest elements will be purged until that is
  381. no longer the case. "Age" is determined via a provided lambda function. It
  382. is a value with arbitrary numeric value that is used comparitively.
  383. The `element_age` lambda takes two arguments: the element index and the
  384. element value. It must return values that can be compared to one another
  385. and be added and subtracted (e.g. int, float, datetime). If the lambda
  386. returns `datetime`s, the `max_age` should be a `timedelta`.
  387. `self.element_age` and `self.max_age` can be modified at runtime,
  388. however elements will only be discarded following the next mutating
  389. operation. Call `self.purge_old_elements()` to force resizing.
  390. """
  391. def __init__(self, max_age, element_age, *args, **kwargs):
  392. super().__init__(*args, **kwargs)
  393. self.max_age = max_age
  394. self.element_age = element_age
  395. self.is_culling = False
  396. def purge_old_elements(self):
  397. """
  398. Manually purges elements. Purging usually occurs automatically, but
  399. this can be called if `self.max_age` or `self.element_age` are modified
  400. at runtime.
  401. """
  402. self.post_mutate()
  403. def pre_mutate(self):
  404. pass
  405. def post_mutate(self):
  406. if self.is_culling or len(self) <= 1:
  407. return
  408. self.is_culling = True
  409. min_age = None
  410. max_age = None
  411. ages = {}
  412. for i, elem in enumerate(self):
  413. age = self.element_age(i, elem)
  414. ages[i] = age
  415. if min_age is None or age < min_age:
  416. min_age = age
  417. if max_age is None or age > max_age:
  418. max_age = age
  419. cutoff = max_age - self.max_age
  420. if min_age >= cutoff:
  421. self.is_culling = False
  422. return
  423. for i in reversed(range(len(self))):
  424. if ages[i] < cutoff:
  425. del self[i]
  426. self.is_culling = False
  427. def copy(self):
  428. return AgeBoundList(self.max_age, self.element_age, super())
  429. class AgeBoundSet(AbstractMutableSet):
  430. """
  431. Subclass of `set` that enforces a maximum "age" of elements.
  432. After each mutating operation, the minimum and maximum "age" of the elements
  433. are determined. If the span between the newest and oldest exceeds
  434. `self.max_age` then then the oldest elements will be purged until that is
  435. no longer the case. "Age" is determined via a provided lambda function. It
  436. is a value with arbitrary numeric value that is used comparitively.
  437. The `element_age` lambda takes one argument: the element value. It must
  438. return values that can be compared to one another and be added and
  439. subtracted (e.g. int, float, datetime). If the lambda returns `datetime`s,
  440. the `max_age` should be a `timedelta`.
  441. `self.element_age` and `self.max_age` can be modified at runtime,
  442. however elements will only be discarded following the next mutating
  443. operation. Call `self.purge_old_elements()` to force resizing.
  444. """
  445. def __init__(self, max_age, element_age, *args, **kwargs):
  446. super().__init__(*args, **kwargs)
  447. self.max_age = max_age
  448. self.element_age = element_age
  449. self.is_culling = False
  450. def purge_old_elements(self):
  451. """
  452. Manually purges elements. Purging usually occurs automatically, but
  453. this can be called if `self.max_age` or `self.element_age` are modified
  454. at runtime.
  455. """
  456. self.post_mutate()
  457. def pre_mutate(self):
  458. pass
  459. def post_mutate(self):
  460. if self.is_culling or len(self) <= 1:
  461. return
  462. self.is_culling = True
  463. min_age = None
  464. max_age = None
  465. ages = {}
  466. for elem in self:
  467. age = self.element_age(elem)
  468. ages[elem] = age
  469. if min_age is None or age < min_age:
  470. min_age = age
  471. if max_age is None or age > max_age:
  472. max_age = age
  473. cutoff = max_age - self.max_age
  474. if min_age >= cutoff:
  475. self.is_culling = False
  476. return
  477. for elem, age in ages.items():
  478. if age < cutoff:
  479. self.remove(elem)
  480. self.is_culling = False
  481. def copy(self):
  482. return AgeBoundSet(self.max_age, self.element_age, super())
  483. class AgeBoundDict(AbstractMutableDict):
  484. """
  485. Subclass of `dict` that enforces a maximum "age" of elements.
  486. After each mutating operation, the minimum and maximum "age" of the elements
  487. are determined. If the span between the newest and oldest exceeds
  488. `self.max_age` then then the oldest elements will be purged until that is
  489. no longer the case. "Age" is determined via a provided lambda function. It
  490. is a value with arbitrary numeric value that is used comparitively.
  491. The `element_age` lambda takes two arguments: the key and value of a pair.
  492. It must return values that can be compared to one another and be added and
  493. subtracted (e.g. int, float, datetime). If the lambda returns `datetime`s,
  494. the `max_age` should be a `timedelta`.
  495. `self.element_age` and `self.max_age` can be modified at runtime,
  496. however elements will only be discarded following the next mutating
  497. operation. Call `self.purge_old_elements()` to force resizing.
  498. """
  499. def __init__(self, max_age, element_age, *args, **kwargs):
  500. super().__init__(*args, **kwargs)
  501. self.max_age = max_age
  502. self.element_age = element_age
  503. self.is_culling = False
  504. def purge_old_elements(self):
  505. """
  506. Manually purges elements. Purging usually occurs automatically, but
  507. this can be called if `self.max_age` or `self.element_age` are modified
  508. at runtime.
  509. """
  510. self.post_mutate()
  511. def pre_mutate(self):
  512. pass
  513. def post_mutate(self):
  514. if self.is_culling or len(self) <= 1:
  515. return
  516. self.is_culling = True
  517. min_age = None
  518. max_age = None
  519. ages = {}
  520. for key, value in self.items():
  521. age = self.element_age(key, value)
  522. ages[key] = age
  523. if min_age is None or age < min_age:
  524. min_age = age
  525. if max_age is None or age > max_age:
  526. max_age = age
  527. cutoff = max_age - self.max_age
  528. if min_age >= cutoff:
  529. self.is_culling = False
  530. return
  531. for key, age in ages.items():
  532. if age < cutoff:
  533. del self[key]
  534. self.is_culling = False
  535. def copy(self):
  536. return AgeBoundDict(self.max_age, self.element_age, super())