desc.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. # -*- coding: utf-8 -*-
  2. '''stuf descriptors.'''
  3. from threading import local
  4. from functools import update_wrapper, partial
  5. from .six import items
  6. from .iterable import exhaustmap
  7. from .deep import selfname, setter, getcls, setpart
  8. class lazybase(object):
  9. '''Base class for lazy descriptors.'''
  10. class _lazyinit(lazybase):
  11. '''Base initializer for lazy descriptors.'''
  12. def __init__(self, method, _wrap=update_wrapper):
  13. super(_lazyinit, self).__init__()
  14. self.method = method
  15. self.name = selfname(method)
  16. _wrap(self, method)
  17. def _set(self, this):
  18. return setter(this, self.name, self.method(this))
  19. class lazy(_lazyinit):
  20. '''Lazily assign attributes on an instance upon first use.'''
  21. def __get__(self, this, that):
  22. return self if this is None else self._set(this)
  23. class lazy_class(_lazyinit):
  24. '''Lazily assign attributes on an class upon first use.'''
  25. def __get__(self, this, that):
  26. return self._set(that)
  27. class lazypartial(lazy):
  28. '''Lazily assign attributes on an instance upon first use.'''
  29. def _set(self, this):
  30. return setter(this, self.name, partial(*self.method(this)))
  31. class lazyset(lazy):
  32. '''Lazily assign attributes with a custom setter.'''
  33. def __init__(self, method, fget=None, _wrap=update_wrapper):
  34. super(lazyset, self).__init__(method)
  35. self.fget = fget
  36. _wrap(self, method)
  37. def __set__(self, this, value):
  38. self.fget(this, value)
  39. def __delete__(self, this):
  40. del this.__dict__[self.name]
  41. def setter(self, func):
  42. self.fget = func
  43. return self
  44. class bothbase(_lazyinit):
  45. '''Base for two-way lazy descriptors.'''
  46. def __init__(self, method, expr=None, _wrap=update_wrapper):
  47. super(bothbase, self).__init__(method)
  48. self.expr = expr or method
  49. _wrap(self, method)
  50. def expression(self, expr):
  51. '''
  52. Modifying decorator that defines a general method.
  53. '''
  54. self.expr = expr
  55. return self
  56. class both(bothbase):
  57. '''
  58. Descriptor that caches results of instance-level results while allowing
  59. class-level results.
  60. '''
  61. def __get__(self, this, that):
  62. return self.expr(that) if this is None else self._set(this)
  63. class either(bothbase):
  64. '''
  65. Descriptor that caches results of both instance- and class-level results.
  66. '''
  67. def __get__(self, this, that):
  68. if this is None:
  69. return setter(that, self.name, self.expr(that))
  70. return self._set(this)
  71. class twoway(bothbase):
  72. '''Descriptor that enables instance and class-level results.'''
  73. def __get__(self, this, that):
  74. return self.expr(that) if this is None else self.method(this)
  75. class readonly(lazybase):
  76. '''Read-only lazy descriptor.'''
  77. def __set__(self, this, value):
  78. raise AttributeError('attribute is read-only')
  79. def __delete__(self, this):
  80. raise AttributeError('attribute is read-only')
  81. class ResetMixin(local):
  82. '''Mixin for reseting descriptors subclassing :class:`lazybase`\.'''
  83. def reset(self):
  84. '''Reset previously accessed :class:`lazybase` attributes.'''
  85. attrs = set(vars(self))
  86. exhaustmap(
  87. delattr,
  88. items(vars(getcls(self))),
  89. lambda x, y: x in attrs and isinstance(y, lazybase),
  90. )
  91. class ContextMixin(ResetMixin):
  92. '''Resetable context manager mixin.'''
  93. def __enter__(self):
  94. return self
  95. class Setter(object):
  96. '''Partial setter.'''
  97. @lazypartial
  98. def _setter(self):
  99. return setpart, self