On Sun, 1 Oct 2017 11:07 am, Bill wrote: > You and Ned are both right. Up until a few minutes ago, I wasn't > thinking about a class having more than 1 attribute that I wanted to > change. And now I realize that __get__ doesn't really make sense in > that context (in the back of my mind was the notion that @property > defined __get__, __set__ and __del__) and I allowed that to obscure my > vision. I was on the verge of giving up anything to do with computers, > forever. : )
property *does* define __get__ and __set__, but not __del__, rather it is called __delete__. Confusing, I know, but neither __del__ nor __delete__ should be used very often. __del__ is the instance destructor. When you have an object: obj = Some_Class() and the last reference to that object goes out of scope, the garbage collector collects the object and reclaims its memory. Before doing so, it calls obj.__del__ if it exists. (That's a simplified version.) __delete__ on the other hand is part of the descriptor protocol, which is how Python manages: - methods - classmethod and staticmethod - property and more. Generally speaking, the descriptor protocol is considered "advanced", not as advanced or scary as metaclasses, but not for beginners either. However, using property is much less complex. Properties are *computed attributes*. Here is a grossly simplified explanation of how Python does attribute look-ups, closer to what Python did in version 1.5 than what it does now, but its a good place to start. There are three things you can so with an attribute: get it, delete it, or set it. (A) When you try getting an attribute: result = obj.spam the interpreter starts by looking in the object's instance namespace for a key 'spam': obj.__dict__['spam'] If it finds it, it returns the associated value and we're done. If not, it next looks at the object's class: obj.__class__.__dict__['spam'] and if not, then it looks in any superclasses, then it looks for a __getattr__ method, and finally if all else fails it raises AttributeError. (B) Deleting an attribute: del obj.spam is pretty much the same. (C) When you try setting an attribute: obj.spam = eggs the interpreter assigns to the instance namespace: obj.__class__.__dict__['spam'] = eggs So that's (roughly) how Python worked back in 1998 or so, and its still conceptually close to what happens now. Now let's introduce an extra layer of complexity: properties[1]. (A) if the result of looking up obj.spam is a property object, then instead of returning the property object itself, the interpreter calls the property's getter method, and returns what it returns. (B) Likewise, deleting the property calls the property's deleter method. (Its rare to bother with one of them, so in practice you can ignore it.) (C) And setting obj.spam to a new value calls the property's setter method, which is intended to handle setting the value somewhere. (Again, I'm ignoring much of the complexity needed to by the actual implementation, in order to focus on just the basic conceptual steps.) > BTW, your example (below) is very nice! I may have seen something > similar before, but I am starting to appreciate it better now. I think > all of this would have made a bit more sense (to me), if instead of just > "@property", the syntax was "@property.getter". One of the most common use for properties is read-only attributes, so the decision was made long ago to have property alone to be equivalent to property.getter. But if you feel the need, you can write: @property.getter def spam(self): ... but if you do, expect people to look at you funny and say "what's that do?". > Now I am forced to ask > the question, why did they use the underscore (on temperature) in the > example on the bottom of this page? Is one forced to introduce new > identifiers in order to define a setter? Forced to? Not exactly. A computed attribute need not have any storage at all, or it could only use public attributes, like my earlier Circle example. Circle didn't use any setters, but I could have let you set the diameter, which in turn would set the radius: circle.radius = 2 assert circle.diameter == 4 circle.diameter == 2 # requires a setter assert circle.radius == 1 Getting that to work is left as an exercise :-) But most commonly, computed attributes need to store some data aside, somewhere. You could use a global variable, or write it to a file, or stick it in a list. All of these things have obvious problems, so the most sensible approach it to stick the data in a private attribute. The interpreter doesn't enforce notions of private/public when it comes to Python classes, but there's a very strong convention that anything starting with a single underscore is private. [1] Technically, the interpreter knows nothing about properties. What it cares about is *descriptors*. Properties are just one kind of descriptor, as are methods. But I'm intentionally not talking about the gory details of descriptors. Feel free to ask if you care, but honestly, you don't need to care unless you are writing your own descriptor class. -- Steve “Cheer up,” they said, “things could be worse.” So I cheered up, and sure enough, things got worse. -- https://mail.python.org/mailman/listinfo/python-list