On Aug 23, 2010, at 1:03 PM, Guido van Rossum wrote:
But hasattr() has a far different set of use cases, so we should explore an alternate solution to the problem. The usual reason that people use hasattr() instead of getattr() is that they want to check for the presence of of a method/attribute without actually running it, binding it, or triggering any other behavior.
As your example shows, property() defeats this intent by actually executing the code. A better behavior would not run the code at all. It would check the dictionaries along the MRO but not execute any descriptors associated with a given key.
IMO, this is a much better solution, more in line with known use cases for hasattr(). If the proposed change when through, it would fail to address the common use case and cause people to start writing their own versions of hasattr() that just scan but do not run code.
Hm... That sounds like scope creep to me. Properties are supposed to be cheap and idempotent. Trying to figure out whether an attribute exists without using __getattr__ is fraught with problems -- as soon as a class overrides __getattr__ or __getattribute__ you're hosed anyway.
I don't have a specific proposal in mind. My main questions are
* Is there anything that hasattr(obj, key) can or should do that can't already be done with getattr(obj, key, None)? If not, do we really need to change anything?
* Why do people typically use hasattr() instead getattr()? Aren't they are really trying to just determine whether a key exists somewhere in the MRO? If so, then doing anything more than that is probably a surprise.
I know my own uses of hasattr() do not expect any exceptions at all. It comes up in duck typing support different handling for different types of inputs. If others are using it the same way, I think it is unlikely that they have unittests to cover the possibility that hasattr() would ever start raising exceptions.
I can vouch that the reason hasattr() catches too many exceptions is that when I first added it (around 1990, I think :-) I wasn't very attuned yet to the problems it could cause.
Fire-up the time machine?
P.S. The current behavior seems to be deeply embedded:
int PyObject_HasAttr(PyObject *o, PyObject *attr_name) Returns 1 if o has the attribute attr_name, and 0 otherwise. This is equivalent to the Python expression hasattr(o, attr_name). This function always succeeds. int PyObject_HasAttrString(PyObject *o, const char *attr_name) Returns 1 if o has the attribute attr_name, and 0 otherwise. This is equivalent to the Python expression hasattr(o, attr_name). This function always succeeds.