__contains__ classmethod?

Peter Otten __peter__ at web.de
Mon Dec 18 17:39:46 EST 2017


Peter Otten wrote:

> Tim Chase wrote:
> 
>> Playing around, I had this (happens to be Py2, but gets the same
>> result in Py3) code
>> 
>> class X(object):
>>   ONE = "one"
>>   TWO = "two"
>>   _ALL = frozenset(v for k,v in locals().items() if k.isupper())
>>   @classmethod
>>   def __contains__(cls, v):
>>     return v in cls._ALL
>> print(dir(X))
>> print(X._ALL)
>> 
>> Running this gives
>> 
>>   ['ONE', 'TWO', '_ALL', '__class__', '__contains__', '__delattr__',
>>   '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
>>   '__getattribute__', '__gt__', '__hash__', '__init__', '__le__',
>>   '__lt__', '__module__', '__ne__', '__new__', '__reduce__',
>>   '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
>>   '__str__', '__subclasshook__', '__weakref__']
>> 
>> And then, depending on whether it's Py2 or Py3, I get either
>> 
>>   frozenset({'one', 'two'})
>>   frozenset(['two', 'one'])
>> 
>> Which I expect.  Hey, look. There's a __contains__ method. And it
>> has been specified as a @classmethod.  So I want to test it:
>> 
>>   print("one" in X)
>> 
>> However that fails with
>> 
>>   Traceback (most recent call last):
>>     File "x.py", line 10, in <module>
>>       print("one" in X)
>>   TypeError: argument of type 'type' is not iterable
>> 
>> My understanding was that "in" makes use of an available __contains__
>> but something seems to preventing Python from finding that.
>> 
>> What's going on here?
> 
> Like __next__ method is looked up in the class, and for the class X that
> would be its metaclass, type. 

Sorry, I realize that this sentence is impossible to understand. Random832 
states the problem more clearly.

[Digression] I mentioned the __next__() method specifically because I 
remembered that you could override its predecessor next() in the instance. 
However, the examples below demonstrate that this behaviour is limited to 
classic classes:

>>> def demo(C):
...     c = C()
...     c.next = lambda: 42
...     for x in c:
...         print x
...         break
... 
>>> class A:
...     def __iter__(self):
...         return self
...     def next(self):
...         return "class"
... 
>>> demo(A)
42
>>> class B(A, object): pass
... 
>>> demo(B)
class


> So you have to adjust that:
> 
> $ cat contains.py
> class XType(type):
>     def __contains__(cls, v):
>         print("metaclass")
>         return v in cls._ALL
> 
> 
> class X(metaclass=XType):
>     ONE = "one"
>     TWO = "two"
>     _ALL = frozenset(v for k, v in locals().items() if k.isupper())
> 
>     @classmethod
>     def __contains__(cls, v):
>         print("instance")

This should rather be

          print("class")

>         return v in cls
> 
> print("one" in X, "one" in X())
> print("three" in X, "three" in X())
> $ python3 contains.py
> metaclass
> instance
> metaclass
> True True
> metaclass
> instance
> metaclass
> False False





More information about the Python-list mailing list