@@ -335,22 +335,6 @@ def get(self) -> Value:
335335 descriptor = self .get_descriptor ()
336336 return descriptor .__get__ (self .owning_object_or_error ())
337337
338- def set (self , value : Value ) -> None :
339- """Set the value of the descriptor.
340-
341- This method may only be called if the DescriptorInfo object is bound to a
342- `.Thing` instance. It will raise an error if called on a class.
343-
344- :param value: the new value.
345-
346- :raises NotBoundToInstanceError: if called on an unbound info object.
347- """
348- if not self .is_bound :
349- msg = f"We can't set the value of { self .name } when called on a class."
350- raise NotBoundToInstanceError (msg )
351- descriptor = self .get_descriptor ()
352- descriptor .__set__ (self .owning_object_or_error (), value )
353-
354338 def __eq__ (self , other : Any ) -> bool :
355339 """Determine if this object is equal to another one.
356340
@@ -404,6 +388,11 @@ class Example:
404388 assert p.name == "my_prop"
405389 assert p.title == "My Property."
406390 assert p.description.startswith("This is")
391+
392+ `.BaseDescriptor` is a "non-data descriptor" (meaning it doesn't implement
393+ ``__set__``). This allows it to be overwritten by assigning to an object's
394+ attribute, which can be useful in test code. This can easily be changed in
395+ subclasses by implementing ``__set__``\ .
407396 """
408397
409398 def __init__ (self ) -> None :
@@ -593,21 +582,6 @@ def instance_get(self, obj: Owner) -> Value:
593582 "See BaseDescriptor.__instance_get__ for details."
594583 )
595584
596- def __set__ (self , obj : Owner , value : Value ) -> None :
597- """Mark the `BaseDescriptor` as a data descriptor.
598-
599- Even for read-only descriptors, it's important to define a ``__set__`` method.
600- The presence of this method prevents Python overwriting the descriptor when
601- a value is assigned. This base implementation returns an `AttributeError` to
602- signal that the descriptor is read-only. Overriding it with a method that
603- does not raise an exception will allow the descriptor to be written to.
604-
605- :param obj: The object on which to set the value.
606- :param value: The value to set the descriptor to.
607- :raises AttributeError: always, as this is read-only by default.
608- """
609- raise AttributeError ("This attribute is read-only." )
610-
611585 def _descriptor_info (
612586 self , info_class : type [DescriptorInfoT ], obj : Owner | None = None
613587 ) -> DescriptorInfoT :
@@ -669,9 +643,35 @@ def value_type(self) -> type[Value]:
669643 """The type of the descriptor's value."""
670644 return self .get_descriptor ().value_type
671645
646+ def set (self , value : Value ) -> None :
647+ """Set the value of the descriptor.
648+
649+ This method may only be called if the DescriptorInfo object is bound to a
650+ `.Thing` instance. It will raise an error if called on a class.
651+
652+ :param value: the new value.
653+
654+ :raises NotBoundToInstanceError: if called on an unbound info object.
655+ """
656+ if not self .is_bound :
657+ msg = f"We can't set the value of { self .name } when called on a class."
658+ raise NotBoundToInstanceError (msg )
659+ descriptor = self .get_descriptor ()
660+ descriptor .__set__ (self .owning_object_or_error (), value )
661+
672662
673663class FieldTypedBaseDescriptor (Generic [Owner , Value ], BaseDescriptor [Owner , Value ]):
674- """A BaseDescriptor that determines its type like a dataclass field."""
664+ r"""A `.BaseDescriptor` that determines its type like a dataclass field.
665+
666+ This adds two things to `.BaseDescriptor`\ :
667+
668+ 1. Descriptors inheriting from this class will inspect the type annotations of
669+ their owning class when determining ``value_type``\ .
670+ 2. This class and its children will be "data descriptors" because there is a
671+ stub implementation of ``__set__``\ . This means that the attribute may not
672+ be assigned to (unless ``__set__`` is overridden). This is the behaviour
673+ that `builtins.property` has.
674+ """
675675
676676 def __init__ (self ) -> None :
677677 """Initialise the FieldTypedBaseDescriptor.
@@ -852,6 +852,21 @@ def descriptor_info(
852852 """
853853 return self ._descriptor_info (FieldTypedBaseDescriptorInfo , owner )
854854
855+ def __set__ (self , obj : Owner , value : Value ) -> None :
856+ """Mark the `BaseDescriptor` as a data descriptor.
857+
858+ Even for read-only descriptors, it's important to define a ``__set__`` method.
859+ The presence of this method prevents Python overwriting the descriptor when
860+ a value is assigned. This base implementation returns an `AttributeError` to
861+ signal that the descriptor is read-only. Overriding it with a method that
862+ does not raise an exception will allow the descriptor to be written to.
863+
864+ :param obj: The object on which to set the value.
865+ :param value: The value to set the descriptor to.
866+ :raises AttributeError: always, as this is read-only by default.
867+ """
868+ raise AttributeError ("This attribute is read-only." )
869+
855870
856871class DescriptorInfoCollection (
857872 Mapping [str , DescriptorInfoT ],
0 commit comments