Issue 13667: contains method behavior (original) (raw)

Created on 2011-12-28 09:11 by JBernardo, last changed 2022-04-11 14:57 by admin. This issue is now closed.

Messages (12)
msg150283 - (view) Author: João Bernardo (JBernardo) * Date: 2011-12-28 09:11
Hi, I'm working on a class which implements the __contains__ method but the way I would like it to work is by generating an object that will be evaluated later. It'll return a custom object instead of True/False class C: def __contains__(self, x): return "I will evaluate this thing later... Don't bother now" but when I do: >>> 1 in C() True It seems to evaluate the answer with bool! Reading the docs (http://docs.python.org/py3k/reference/expressions.html#membership-test-details) It says: "`x in y` is true if and only if `y.__contains__(x)` is true." It looks like the docs doesn't match the code and the code is trying to mimic the behavior of lists/tuples where "x in y" is the same as any(x is e or x == e for e in y) and always yield True or False. There is a reason why it is that way? Thanks!
msg150284 - (view) Author: Georg Brandl (georg.brandl) * (Python committer) Date: 2011-12-28 09:24
"an object is true" is a short way of saying "bool(obj) is True". So the docs match the behavior. Returning the actual object instead of True/False from the "in" operator is a feature request.
msg150285 - (view) Author: João Bernardo (JBernardo) * Date: 2011-12-28 09:29
@Georg Brandl Oh sorry, now I see... true != True But still, why is that the default behavior? Shouldn't it use whatever the method returns?
msg150286 - (view) Author: Georg Brandl (georg.brandl) * (Python committer) Date: 2011-12-28 10:37
Well, usually what you want *is* a boolean indicating whether the element is in the collection or not. Being able to overload "in", mostly for metaprogramming purposes, is a request that probably nobody thought of when implementing "in".
msg150296 - (view) Author: Benjamin Peterson (benjamin.peterson) * (Python committer) Date: 2011-12-28 16:01
I think the idea has some merit. I think it should be well vetted on python-ideas, though. One thing that will certianly weigh against it is that implementation would not be trivial.
msg150303 - (view) Author: João Bernardo (JBernardo) * Date: 2011-12-28 16:58
I see that every other comparison operator (<, >, <=, >=, ==, !=) except for `is` work the way I expect and is able to return anything. e.g. >>> numpy.arange(5) < 3 array([ True, True, True, False, False], dtype=bool) I didn't checked the code (and probably I'm talking nonsense), but seems like the `in` operator has an extra call to `PyObject_IsTrue` that maybe could be dropped? Of course it can break code relying on `x in y` being True/False but it would only happen on customized classes. Another option that won't break code is to add a different method to handle these cases. Something like "__contains_non_bool__", but that'd be a big api change.
msg150304 - (view) Author: Benjamin Peterson (benjamin.peterson) * (Python committer) Date: 2011-12-28 17:01
2011/12/28 João Bernardo <report@bugs.python.org>: > > João Bernardo <jbvsmo@gmail.com> added the comment: > > I see that every other comparison operator (<, >, <=, >=, ==, !=) except for `is` work the way I expect and is able to return anything. > > e.g. > >>>> numpy.arange(5) < 3 > array([ True,  True,  True, False, False], dtype=bool) > > I didn't checked the code (and probably I'm talking nonsense), but seems like the `in` operator has an extra call to `PyObject_IsTrue` that maybe could be dropped? I'm not sure what you're referring to, but I doubt that would do the job. > > Of course it can break code relying on `x in y` being True/False but it would only happen on customized classes. > > Another option that won't break code is to add a different method to handle these cases. Something like "__contains_non_bool__", but that'd be a big api change. And completely hideous.
msg150307 - (view) Author: João Bernardo (JBernardo) * Date: 2011-12-28 17:22
Using my poor grep abilities I found that on Objects/typeobject.c (I replaced some declarations/error checking from the code with ...) static int slot_sq_contains(PyObject *self, PyObject *value) { ... func = lookup_maybe(self, "__contains__", &contains_str); if (func != NULL) { ... res = PyObject_Call(func, args, NULL); ... if (res != NULL) { result = PyObject_IsTrue(res); Py_DECREF(res); } } else if (! PyErr_Occurred()) { /* Possible results: -1 and 1 */ result = (int)_PySequence_IterSearch(self, value, PY_ITERSEARCH_CONTAINS); } } I don't know if I'm in the right place, but the function returns `int` and evaluates the result to 1 or 0 if __contains__ is found. I also don't know what SQSLOT means, but unlike the other operators (which are defined as TPSLOT), `slot_sq_contains` is a function returning "int" while `slot_tp_richcompare` returns "PyObject *". Why is that defined that way?
msg150309 - (view) Author: Georg Brandl (georg.brandl) * (Python committer) Date: 2011-12-28 18:08
It's defined that way because it's a slot returning a bool, so it doesn't need to return anything except for 0 or 1. Changing this to return a PyObject would mean that every extension module (i.e. module written in C) that defines a custom __contains__ would need to be adapted. That is the non-trivial implementation that Benjamin was talking about.
msg150314 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2011-12-28 20:25
-1 on this proposal. It has everyone paying a price for a questionable feature that would benefit very few.
msg150315 - (view) Author: Alex Gaynor (alex) * (Python committer) Date: 2011-12-28 20:31
For what it's worth I proposed this on -ideas a while ago, the sticking points were what does `not in` do (no one had an answer anyone was happy with for this), and do we need a way to override it from the other perspective (e.g. if I want to do `SpecialObj() in [1, 2, 3]`, is there a way to do that?).
msg150321 - (view) Author: João Bernardo (JBernardo) * Date: 2011-12-28 22:33
The problem with `not in` is because it must evaluate the result. It's not just another operator like "==" and "!=". Looks like we're suffering from premature optimization and now it would break a lot of code to make it good. For my application, I created a different method to generate the object (Not as good as I wanted, but there's no option right now): `MyClass().has(1)` instead of `1 in MyClass()` So, if no one comes up with a better idea, this issue should be closed. Thanks
History
Date User Action Args
2022-04-11 14:57:25 admin set github: 57876
2011-12-28 22:35:01 benjamin.peterson set status: open -> closedresolution: rejected
2011-12-28 22:33:51 JBernardo set messages: +
2011-12-28 20:31:28 alex set nosy: + alexmessages: +
2011-12-28 20:25:09 rhettinger set nosy: + rhettingermessages: +
2011-12-28 18:08:10 georg.brandl set messages: +
2011-12-28 17:22:54 JBernardo set messages: +
2011-12-28 17:01:13 benjamin.peterson set messages: +
2011-12-28 16:58:28 JBernardo set messages: + components: - Documentation
2011-12-28 16:01:38 benjamin.peterson set messages: +
2011-12-28 10:37:10 georg.brandl set nosy: + pitrou, benjamin.peterson, - docs@pythonmessages: + versions: - Python 3.2
2011-12-28 09:34:24 JBernardo set type: behavior -> enhancement
2011-12-28 09:29:19 JBernardo set type: enhancement -> behaviormessages: + versions: + Python 3.2
2011-12-28 09:24:37 georg.brandl set versions: - Python 3.2nosy: + georg.brandlmessages: + assignee: docs@python -> type: behavior -> enhancement
2011-12-28 09:11:10 JBernardo create