[Python-Dev] __contains__ hook

Guido van Rossum guido@CNRI.Reston.VA.US
Wed, 02 Feb 2000 11:35:03 -0500


Moshe seems eager to get comments on this post :-)

> Here's a very preliminary, very hackish version of a hook for the "in"
> operator. 
> 
> Basically, use like:
> 
> class spam:
> 	def __contains__(self, o):
> 		return 1
> 6 in spam() (answers 1)
> 
> I must say I was horrified by the current way the operator was handled:
> very non-OO-ish. I'd much rather there'd be a slot in the sequence
> interface for this method. This is why there's still no way to use the
> hook with regular C extension types.
> 
> Have fun!
> 
> (BTW: I've tested it only minimally, so it might break your Python. Use
> with caution).
> 
> PS.
> Eric, you can implement sets the *right* way this time.

For those who, like me, are too lazy to unpack attachments, here's the
text of Moshe's patch:

> *** ../../../python/dist/src/Objects/abstract.c	Fri Oct 15 14:09:02 1999
> --- Objects/abstract.c	Tue Feb  1 10:34:34 2000
> ***************
> *** 1110,1115 ****
> --- 1110,1140 ----
>   		}
>   		return 0;
>   	}
> + 	/* special case for instances. Basically emulating Python code,
> + 	   but optimizations will come later */
> + 	if (PyInstance_Check(w)) {
> + 		PyObject *py__contains__, *py_ret, *py_args;
> + 		int ret;
> + 
> + 		py__contains__ = PyObject_GetAttrString(w, "__contains__");
> + 		if(py__contains__ == NULL) 
> + 			return -1;
> + 		py_args = PyTuple_New(1);
> + 		if(py_args == NULL) {
> + 			Py_DECREF(py__contains__);
> + 			return -1;
> + 		}
> + 		Py_INCREF(v);
> + 		PyTuple_SET_ITEM(py_args, 0, v);
> + 		py_ret = PyObject_CallObject(py__contains__, py_args);
> + 		Py_DECREF(py__contains__);
> + 		Py_DECREF(py_args);
> + 		if(py_ret == NULL) 
> + 			return -1;
> + 		ret = PyObject_IsTrue(py_ret);
> + 		Py_DECREF(py_args);
> + 		return ret;
> + 	}
>   
>   	sq = w->ob_type->tp_as_sequence;
>   	if (sq == NULL || sq->sq_item == NULL) {

I like the idea of overloading 'in' (and 'not in') with __contains__.
There are several issues with this patch though (apart from the fact
that he left out the disclaimer from
http://www.python.org/1.5/bugrelease.html :-).

First of all, it actually breaks 'in' for descendants of UserList, and
other classes that define __getitem__ but not __contains__.  That's
easily fixed by clearing the error and jumping forward instead of
returning an error when the GetAttrString() call fails.

Second, it's customary to define a static object variable initialized
to NULL, which is set to the interned string object; this speeds up
the lookup a bit using PyObject_GetAttr().

Micro-nit: I want a space between 'if' and '('.  It just looks better.

But the real issue is what Moshe himself already brings up: contains
should have a slot in the type struct, so extension types can also
define this.

Moshe, do you feel like doing this right?

--Guido van Rossum (home page: http://www.python.org/~guido/)