_immutable_attr.py 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
  2. # This implementation of the immutable decorator is for python 3.6,
  3. # which doesn't have Context Variables. This implementation is somewhat
  4. # costly for classes with slots, as it adds a __dict__ to them.
  5. import inspect
  6. class _Immutable:
  7. """Immutable mixin class"""
  8. # Note we MUST NOT have __slots__ as that causes
  9. #
  10. # TypeError: multiple bases have instance lay-out conflict
  11. #
  12. # when we get mixed in with another class with slots. When we
  13. # get mixed into something with slots, it effectively adds __dict__ to
  14. # the slots of the other class, which allows attribute setting to work,
  15. # albeit at the cost of the dictionary.
  16. def __setattr__(self, name, value):
  17. if not hasattr(self, '_immutable_init') or \
  18. self._immutable_init is not self:
  19. raise TypeError("object doesn't support attribute assignment")
  20. else:
  21. super().__setattr__(name, value)
  22. def __delattr__(self, name):
  23. if not hasattr(self, '_immutable_init') or \
  24. self._immutable_init is not self:
  25. raise TypeError("object doesn't support attribute assignment")
  26. else:
  27. super().__delattr__(name)
  28. def _immutable_init(f):
  29. def nf(*args, **kwargs):
  30. try:
  31. # Are we already initializing an immutable class?
  32. previous = args[0]._immutable_init
  33. except AttributeError:
  34. # We are the first!
  35. previous = None
  36. object.__setattr__(args[0], '_immutable_init', args[0])
  37. try:
  38. # call the actual __init__
  39. f(*args, **kwargs)
  40. finally:
  41. if not previous:
  42. # If we started the initialization, establish immutability
  43. # by removing the attribute that allows mutation
  44. object.__delattr__(args[0], '_immutable_init')
  45. nf.__signature__ = inspect.signature(f)
  46. return nf
  47. def immutable(cls):
  48. if _Immutable in cls.__mro__:
  49. # Some ancestor already has the mixin, so just make sure we keep
  50. # following the __init__ protocol.
  51. cls.__init__ = _immutable_init(cls.__init__)
  52. if hasattr(cls, '__setstate__'):
  53. cls.__setstate__ = _immutable_init(cls.__setstate__)
  54. ncls = cls
  55. else:
  56. # Mixin the Immutable class and follow the __init__ protocol.
  57. class ncls(_Immutable, cls):
  58. @_immutable_init
  59. def __init__(self, *args, **kwargs):
  60. super().__init__(*args, **kwargs)
  61. if hasattr(cls, '__setstate__'):
  62. @_immutable_init
  63. def __setstate__(self, *args, **kwargs):
  64. super().__setstate__(*args, **kwargs)
  65. # make ncls have the same name and module as cls
  66. ncls.__name__ = cls.__name__
  67. ncls.__qualname__ = cls.__qualname__
  68. ncls.__module__ = cls.__module__
  69. return ncls