I am testing the following test script. As you know, the __getattr__ method gets called when fetching previously undefined attributes. However, if the __setattr__ is added (uncommented), the __getattr__ method is still called even for existing attributes.
Can someone please help in making sense of this.
Here is the test script:
class Catcher:
def __init__(self):
self.name = 100
self.other = 500
self.planet = 'earth'
print('\nDone instantiation.\n')
def __getattr__(self, name): # Called if fetching attribute
print(f"Get: {name}") # that does not yet exist
# Comment / uncomment method for testing purposes
def __setattr__(self, name, value):
print(f'Set: {name} {value}')
X = Catcher()
X.job # Prints "Get: job"
X.job = 'carpenter'
X.pay # Prints "Get: pay"
X.pay = 99 # Prints "Set: pay 99"
print('\nThese two attrs already exist:')
X.job # '__getattr__' should not be called since it now exists
X.pay # '__getattr__' should not be called since it now exists
X.name
The problem in your code is that the __setattr__ method has to actually set attributes in order for them to get set. __setattr__ is not a âhookâ for additional work to do when an attribute is set. It is a replacement for the attribute-setting logic. So if you want the attribute to actually get set, you need to do it yourself - for example, by explicitly calling the object.__setattr__ logic.
Itâs like this so that you can set a different attribute name internally, or conditionally reject an attempt to set the attribute, etc.
I get a bit confused in your description, itâs not clear to me that all of your g-s and s-s are straight
To hopefully answer the question anyway, the __getattr__ method is only called if the attribute is not found in the objectâs __dict__ when an attempt is made to access it, but __setattr__ is always called when an attempt is made to set an attribute. Note that your __setattr__ implementation is not actually doing anything, so the attribute is not set and __getattr__ will still be called because X.job and X.pay still donât exist.
Yes, this is the ticket. However, what do you mean by âon the superclassâ if I donât have a superclass that I am inheriting? Can you please elaborate further so that it may be better understood.
Yup, I got them straightened out. Sometimes there is a misfire with the neurons upstairs.
I actually performed introspection, and it shows that they are part of instance attributes. For example, if I comment out the __setattr__ method, and perform the following:
print('\n--- Starting introspection.\n')
for key in X.__dict__:
print(f"{key:<6} {getattr(X, key):>2}") # key-value pair
print('\n--- End introspection.')
We see that they are part of the __dict__ and attributes of the instance.
Oh, ok. I believe there is theory up ahead regarding this concept. I appreciate your insight.
All of the feedback collectively helps me understand this a bit better.
Yes, I have been performing additional testing. The __setattr__ method as it was currently set-up, wasnât actually doing any assignments (if it is in the code, it overrides the current default attribute-setting logic as @kknechtel had correctly pointed out). Via the output print statements, as they were set up, were giving the false impression that assignments were taking place when they actually werenât.
When I stated that introspection showed the attributes listed in the __dict__, it was because I was commenting out the __setattr__ method thereby allowing Pythonâs default attribute-assigning logic to be applied. Again, my understanding at that time was incorrect.