[Tuure Laurinolli] > Someone pasted the original version of the following code snippet on > #python today. I started investigating why the new-style class didn't > work as expected > classes apparently don't return true for PyInstance_Check, which causes > a problem in PySequence_Check, since it will only do an attribute lookup > for instances. > > Things probably shouldn't be this way. Should I go to python-dev with this?
PyInstance_Check is defined as checking for instances of old style classes. Hence, it appropriate that it does not return True for instances of new-style classes. PySequence_Check is pretty good shape. In general, it is not always possible to tell a mapping from a sequence, but the function does try to take advantage of all available information. In particular, it has a separate code path for new-style instances which sometimes have additional information (i.e. dict and its subclasses fill the tp_as_mapping->mp_subscript slot instead of the tp_as_sequence->sq_slice slot). Old style classes never inherit from dict, tuple, list, etc. so that slot distinction isn't meaningful. Accordingly, PySequence_Check simply checks to see if an object defines __getitem__ and that check entails an attribute lookup. The posted code snippet uses a __getattr__ gimmick to supply an affirmative answer to that check. The docs do not make guarantees about how iter(o) will determine whether object o is a sequence. Currently, it does the best it can and that entails having different implementation logic for new-style and old-style objects as described above. The provided snippet uses the __getattr__ gimmick to reveal the implementation specific behavior. Mystery solved. In contrast, the docs do make guarantees about explicit attribute lookup (i.e. that an attribute lookup will actually occur and that slot checking logic will not be substituted). The posted snippet reveals that these guarantees are intact for both new and old-style classes. Knowing all of this reveals the behavior to be a feature rather than a bug -- the feature being that operator.IsMappingType and operator.IsSequenceType do a remarkable job (few false positives and no false negatives) across a huge variety of object types. It has been tested with instances of set, int, float, complex, long, bool, str, unicode, UserString, list, UserList, tuple, deque, NoneType, NotImplemented, new and old style instances with and without defining __getitem__. Also, tested were subclasses of the above types. The false positives are limited to user defined classes (new or old-style) defining __getitem__. Since the mapping API overlaps with the sequence API, it is not always possible to distinguish the two. Of course, you found that it is possible to throw a monkey wrench in the process using __getattr__ to exploit undefined, implementation specific behavior. Don't do that. If a class needs to be iterable, then supply an __iter__ method. If that method needs to be retargeted dynamically, then take advantage of the guarantee that iter(o) will always find a defined __iter__ method. Perhaps, define __iter__ as lambda obj: obj(obj.__getattr__(obj, '__iter__')) or somesuch. IOW, to guarantee that iter() performs an __iter__ lookup, fill the slot with something that does that lookup. Raymond Hettinger E = mc**2 # Einstein E = IR # Ohm's law therefore IR = mc**2 # Raymond's grand unified theory -- http://mail.python.org/mailman/listinfo/python-list