Descriptor __get__ and __set__ argument discrepancy

Hi all, Quick question. def __get__(self, instance, owner): def __set__(self, instance, value): Is there a reason why `__set__` does not have owner in it’s arguments while `__get__` does? Regards, DG

On 19/10/2023 18.29, Dom Grigonis wrote:
Is this a Python Idea? You may have only given us a couple of lines, when the scope of the question is much wider... Be careful because these mechanisms were updated relatively-recently - and thus there are differences between Python versions! From my notes (please see code-example which appears to answer your question): The __set_name__() method is a special method in Python that is used in the context of descriptors. It was introduced in Python 3.6 as a part of the Descriptor Protocol. The purpose of the __set_name__() method is to allow descriptors to automatically determine and store the name of the attribute they are assigned to within the class. This method is called once during the creation of the descriptor instance, and it receives two arguments: the owner class and the name of the attribute. By implementing the __set_name__() method in a descriptor, you can access and store the name of the attribute to which the descriptor is assigned. This can be useful when you want to associate the descriptor with the attribute name or perform any additional setup based on the attribute name. Here's an example to illustrate the usage of __set_name__(): ```python class Descriptor: def __set_name__(self, owner, name): self.name = name def __get__(self, instance, owner): if instance is None: return self return instance.__dict__.get(self.name) def __set__(self, instance, value): instance.__dict__[self.name] = value class MyClass: attribute = Descriptor() obj = MyClass() obj.attribute = 42 print(obj.attribute) # Output: 42 ``` In the above code, the Descriptor class defines the __set_name__() method. When the attribute descriptor is assigned to the attribute attribute of the MyClass class, the __set_name__() method is automatically called with the owner class (MyClass) and the attribute name (attribute). Inside this method, we store the attribute name in the descriptor instance. Later, when we set obj.attribute = 42, the descriptor's __set__() method is called, and the value is stored in the instance's __dict__ attribute using the previously stored attribute name. By using __set_name__(), descriptors can dynamically associate themselves with the attribute names they are assigned to, providing more flexibility and customization. Web.Refs: https://docs.python.org/3/howto/descriptor.html https://docs.python.org/3/reference/datamodel.html#descriptors -- Regards, =dn

On 19/10/2023 19.50, Dom Grigonis wrote:
Thank you,
Good information, thank you. Was not aware of __set_name__.
IIRC that was one of the updates/improvements. Thanks to whomsoever...! The: instance.__dict__[self.name] = value may require a bit of thought before it feels comfortable, but it is significantly easier to understand than what we had to do 'before'. Another surprise, and I've assumed you're asking in the context of [Custom] Descriptors, is in how many places/functions Python makes use of a descriptor/descriptor protocol. Yet few of us seem to make use of them in our application code... (YMMV!) -- Regards, =dn

However, I was more interested, why doesn't __set__ have an `owner` argument, while `__get__` does. I am aware that this is not an issue at all as one can simply do `inst.__class__`, but I am just curious about the reason for inconsistency. Although, answers that I got were very useful. DG

Hi, The __get__ method of descriptors can be called at the class level (that's how methods work) and in that case instance would be None, but owner will always reference the current class. __set__ can only be called for instances on that class (`Cls.attr = ...` would redefine the class-attribute and not call the descriptor), so instance can never be None and owner is type(instance). Le jeu. 19 oct. 2023 à 09:45, Dom Grigonis <dom.grigonis@gmail.com> a écrit :
-- Antoine Rozo

On 19/10/2023 20.43, Dom Grigonis wrote:
IIRC that's the way we used to have to do things, ie 'turn ourselves in knots'! Because coding a Custom Descriptor is still something of a mind-bender, I coded a 'library' ABC/Super-class ("something I put in the oven earlier"*) which is slightly more sophisticated that the code-example (see earlier) PLUS a call to, and outline-code for an @abstractmethod called validate(). Care to guess its purpose!? This means that when a Custom Descriptor is useful, it is a matter of (trust and) sub-classing, eg NonNegativeInteger( SuperClassName ), which provides an appropriate, concrete, validate() method. "Re-use" = no fuss, no muss! - more to the point, my future-self doesn't have to remember the intricacies of the 'new' (v3.6+) internal mechanisms... (the ABC is about 40-lines of code. I'll post it upon demand, or perhaps better off-list...) * this is a saying originating in (British) television cooking-shows, to explain the temporal distortion of how the cook went from a mix of uncooked ingredients to the finished article, without the waiting-time for it to cook - as we would in real-life.
+1 I use them to abstract-away almost all data-item data-validation.
However, I was more interested, why doesn't __set__ have an `owner` argument, while `__get__` does. I am aware that this is not an issue at all as one can simply do `inst.__class__`, but I am just curious about the reason for inconsistency.
@Antoine has given a technical explanation. The next contribution to your thinking is probably to mention/remind that the "owner" argument (in the 'getter') is optional - can provide it in __set_name__() and record it there. Thus, the effective-signatures become: def __get__( self, instance, ) ... def __set__( self, instance, value, ) ... Is this a more familiar pattern? Perhaps the 'mistake' is not in an apparent lack of consistency, but in our expectation of a 'tidy' pattern? There's always going to be a difference in the two signatures, because the 'setter' must be provided with a value, whereas the 'getter' returns (cf accepts) a data-value. Still have questions? Let's take a step (or two) backwards:- Most of us start (down this road) by being introduced to @property. A favorite (realistic) example is "age". Despite (sub-standard) text-books featuring personnel records that use such a field, no professional ever does! We record the person's date-of-birth, and thereafter compute the difference from today() to arrive at (today's) age. The neat 'discovery' is that when using object.age as a property, although the mechanism has been coded as a method, its use is indistinguishable from a 'normal' data-attribute. Smooth! The next step in one's education is to add a 'setter'. The tutorial-example here might be a physical quantity, eg how many bars/tablets/blocks of chocolate you are buying (to give to me, of course). This must be a positive number (there's a (in)famous Amazon bug where they went 'live' allowing negative-quantities!!!). In the case of chocolate, it might be an integer. In the case of other products it might be a decimal/float, eg 1.5KG/lbs of flour. (yes, that would be to mix-up a chocolate cake!). This extension to the first @property use-case, is likely the performance of some-sort of 'validation routine' to make sure that the data is fit-for-purpose ("data-cleaning", "data-validation", etc). The original @property mechanism swings into action when we ask for the value. In this case, the mechanism applies when we set the data-value. Thus, 5 is an acceptable quantity, but "five" is not - and the code is designed to test for such. You know all this, and that an @property is 'syntactic sugar' for a Descriptor as the underlying mechanism. So, now extend that final step, so that instead of an @property with 'getter' and 'setter', code the data-item (actually, its type) as a Descriptor. Now, remember that a data-item which is a Descriptor is just like any other. To set its value, the code is: instance.quantity = 1.5 # ie 1.5KG of flour An "expression" where the name of the data-item is on the left-hand-side, and the value is the RHS. Whereas, when we come to utilise same, eg line_price = quantity * unit_price data-items on the RHS only need to be identified by their name! So, the 'setter' provides two inputs - name and value. Whereas, the 'getter' only needs to know which name (applies to that data-item). The 'pattern' or (necessary) consistency is only the name/id of the data-item. Its value is 'inconsistent' in that it is either being passed-in ('setter') or pulled-out ('getter'). In one case it is an argument and in the other a return-value.
Although, answers that I got were very useful.
Thanks! -- Regards, =dn

On 19/10/2023 18.29, Dom Grigonis wrote:
Is this a Python Idea? You may have only given us a couple of lines, when the scope of the question is much wider... Be careful because these mechanisms were updated relatively-recently - and thus there are differences between Python versions! From my notes (please see code-example which appears to answer your question): The __set_name__() method is a special method in Python that is used in the context of descriptors. It was introduced in Python 3.6 as a part of the Descriptor Protocol. The purpose of the __set_name__() method is to allow descriptors to automatically determine and store the name of the attribute they are assigned to within the class. This method is called once during the creation of the descriptor instance, and it receives two arguments: the owner class and the name of the attribute. By implementing the __set_name__() method in a descriptor, you can access and store the name of the attribute to which the descriptor is assigned. This can be useful when you want to associate the descriptor with the attribute name or perform any additional setup based on the attribute name. Here's an example to illustrate the usage of __set_name__(): ```python class Descriptor: def __set_name__(self, owner, name): self.name = name def __get__(self, instance, owner): if instance is None: return self return instance.__dict__.get(self.name) def __set__(self, instance, value): instance.__dict__[self.name] = value class MyClass: attribute = Descriptor() obj = MyClass() obj.attribute = 42 print(obj.attribute) # Output: 42 ``` In the above code, the Descriptor class defines the __set_name__() method. When the attribute descriptor is assigned to the attribute attribute of the MyClass class, the __set_name__() method is automatically called with the owner class (MyClass) and the attribute name (attribute). Inside this method, we store the attribute name in the descriptor instance. Later, when we set obj.attribute = 42, the descriptor's __set__() method is called, and the value is stored in the instance's __dict__ attribute using the previously stored attribute name. By using __set_name__(), descriptors can dynamically associate themselves with the attribute names they are assigned to, providing more flexibility and customization. Web.Refs: https://docs.python.org/3/howto/descriptor.html https://docs.python.org/3/reference/datamodel.html#descriptors -- Regards, =dn

On 19/10/2023 19.50, Dom Grigonis wrote:
Thank you,
Good information, thank you. Was not aware of __set_name__.
IIRC that was one of the updates/improvements. Thanks to whomsoever...! The: instance.__dict__[self.name] = value may require a bit of thought before it feels comfortable, but it is significantly easier to understand than what we had to do 'before'. Another surprise, and I've assumed you're asking in the context of [Custom] Descriptors, is in how many places/functions Python makes use of a descriptor/descriptor protocol. Yet few of us seem to make use of them in our application code... (YMMV!) -- Regards, =dn

However, I was more interested, why doesn't __set__ have an `owner` argument, while `__get__` does. I am aware that this is not an issue at all as one can simply do `inst.__class__`, but I am just curious about the reason for inconsistency. Although, answers that I got were very useful. DG

Hi, The __get__ method of descriptors can be called at the class level (that's how methods work) and in that case instance would be None, but owner will always reference the current class. __set__ can only be called for instances on that class (`Cls.attr = ...` would redefine the class-attribute and not call the descriptor), so instance can never be None and owner is type(instance). Le jeu. 19 oct. 2023 à 09:45, Dom Grigonis <dom.grigonis@gmail.com> a écrit :
-- Antoine Rozo

On 19/10/2023 20.43, Dom Grigonis wrote:
IIRC that's the way we used to have to do things, ie 'turn ourselves in knots'! Because coding a Custom Descriptor is still something of a mind-bender, I coded a 'library' ABC/Super-class ("something I put in the oven earlier"*) which is slightly more sophisticated that the code-example (see earlier) PLUS a call to, and outline-code for an @abstractmethod called validate(). Care to guess its purpose!? This means that when a Custom Descriptor is useful, it is a matter of (trust and) sub-classing, eg NonNegativeInteger( SuperClassName ), which provides an appropriate, concrete, validate() method. "Re-use" = no fuss, no muss! - more to the point, my future-self doesn't have to remember the intricacies of the 'new' (v3.6+) internal mechanisms... (the ABC is about 40-lines of code. I'll post it upon demand, or perhaps better off-list...) * this is a saying originating in (British) television cooking-shows, to explain the temporal distortion of how the cook went from a mix of uncooked ingredients to the finished article, without the waiting-time for it to cook - as we would in real-life.
+1 I use them to abstract-away almost all data-item data-validation.
However, I was more interested, why doesn't __set__ have an `owner` argument, while `__get__` does. I am aware that this is not an issue at all as one can simply do `inst.__class__`, but I am just curious about the reason for inconsistency.
@Antoine has given a technical explanation. The next contribution to your thinking is probably to mention/remind that the "owner" argument (in the 'getter') is optional - can provide it in __set_name__() and record it there. Thus, the effective-signatures become: def __get__( self, instance, ) ... def __set__( self, instance, value, ) ... Is this a more familiar pattern? Perhaps the 'mistake' is not in an apparent lack of consistency, but in our expectation of a 'tidy' pattern? There's always going to be a difference in the two signatures, because the 'setter' must be provided with a value, whereas the 'getter' returns (cf accepts) a data-value. Still have questions? Let's take a step (or two) backwards:- Most of us start (down this road) by being introduced to @property. A favorite (realistic) example is "age". Despite (sub-standard) text-books featuring personnel records that use such a field, no professional ever does! We record the person's date-of-birth, and thereafter compute the difference from today() to arrive at (today's) age. The neat 'discovery' is that when using object.age as a property, although the mechanism has been coded as a method, its use is indistinguishable from a 'normal' data-attribute. Smooth! The next step in one's education is to add a 'setter'. The tutorial-example here might be a physical quantity, eg how many bars/tablets/blocks of chocolate you are buying (to give to me, of course). This must be a positive number (there's a (in)famous Amazon bug where they went 'live' allowing negative-quantities!!!). In the case of chocolate, it might be an integer. In the case of other products it might be a decimal/float, eg 1.5KG/lbs of flour. (yes, that would be to mix-up a chocolate cake!). This extension to the first @property use-case, is likely the performance of some-sort of 'validation routine' to make sure that the data is fit-for-purpose ("data-cleaning", "data-validation", etc). The original @property mechanism swings into action when we ask for the value. In this case, the mechanism applies when we set the data-value. Thus, 5 is an acceptable quantity, but "five" is not - and the code is designed to test for such. You know all this, and that an @property is 'syntactic sugar' for a Descriptor as the underlying mechanism. So, now extend that final step, so that instead of an @property with 'getter' and 'setter', code the data-item (actually, its type) as a Descriptor. Now, remember that a data-item which is a Descriptor is just like any other. To set its value, the code is: instance.quantity = 1.5 # ie 1.5KG of flour An "expression" where the name of the data-item is on the left-hand-side, and the value is the RHS. Whereas, when we come to utilise same, eg line_price = quantity * unit_price data-items on the RHS only need to be identified by their name! So, the 'setter' provides two inputs - name and value. Whereas, the 'getter' only needs to know which name (applies to that data-item). The 'pattern' or (necessary) consistency is only the name/id of the data-item. Its value is 'inconsistent' in that it is either being passed-in ('setter') or pulled-out ('getter'). In one case it is an argument and in the other a return-value.
Although, answers that I got were very useful.
Thanks! -- Regards, =dn
participants (3)
-
Antoine Rozo
-
dn
-
Dom Grigonis