0

Given the following class:

import operator
class Foo(object):
    def __init__(self, bah):
        self.bah = bah      

    def __invert__(self):
        return {'not': self.bah}

    def __repr__(self):
        return self.bah

    def __or__(self, other):
        return {'or': [self.bah, other]}    


x = Foo('obj1')
y = Foo('obj2')

I can do:

operator.inv(x) # ~x

which gives me:

{'not': 'obj1'}

I can do:

operator.or_(x, ~y) # x | ~y

which gives me:

{'or': ['obj1', {'not': 'obj2'}]}

But why I cannot do:

operator.or_(~x, y) # ~x | y

which throws the following error:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-175-92fdd35dc3b3> in <module>
----> 1 operator.or_(~x, y)

TypeError: unsupported operand type(s) for |: 'dict' and 'Foo'

And how would I be able to output the following?

{'or': [{'not': 'obj1'}, 'obj2']}
Mattijn
  • 12,975
  • 15
  • 45
  • 68
  • 2
    Your operators should be returning instances of `Foo`, so that you can invoke further operators on them. – jasonharper Sep 26 '19 at 21:05
  • The `or` operator evaluates its operands from the left. `~x` is evaluated first and then the `or` is applied. Since the `~x` returns a `dict` from your definition in the `Foo` class, the `or` will be applied to a `dict` and a `Foo` which is undefined. Ref: https://docs.python.org/3/reference/expressions.html#or – rdas Sep 26 '19 at 21:09

3 Answers3

2

You need to overload __ror__ in this case. See this post for details on how Python evaluates operators.

Basically, this statement

operator.__or__(~x, y)

is the same as

x.__invert__().__or__(y)

Since __or__ is not defined for dict objects returned by x.__invert__(), the call fails. Defining __ror__ would make the Python interpreter try to evaluate

y.__ror__(x.__invert__())

for ~x | y after the first attempt fails.

GZ0
  • 4,055
  • 1
  • 10
  • 21
  • Thanks! This is great! `__ror__` as reverse OR. This outputs `{'or': ['obj2', {'not': 'obj1'}]}` which is fine – Mattijn Sep 26 '19 at 21:20
  • Hmm and what about `operator.__or__(~x, ~y)`? I will think about this one as well. But it gives me `TypeError: unsupported operand type(s) for |: 'dict' and 'dict'` – Mattijn Sep 26 '19 at 21:25
  • Well, if that needs to be supported then `__or__` and `__ror__` should not return `dict` objects. They should return `Foo` objects or other objects which support the or operation. – GZ0 Sep 26 '19 at 21:28
  • You are right. Will try to figure out to get `{'or': [{'not': 'obj1'}, {'not':'obj2'}]}` also working – Mattijn Sep 26 '19 at 21:35
  • See also for example: https://stackoverflow.com/questions/6892616/python-multiplication-override/6892635#6892635 – Karl Knechtel Sep 26 '19 at 22:16
0

It seems that the operator.or_() call looks for the __or__ method defined in the first argument that you give. That's why in the operator.or_(x, ~y) case it works fine - x is the Foo object that contains the __or__. definition.

In the second, operator.or_(~x, y), case, however, the output of ~x is a dictionary {'not': 'obj1'} which does not contain the __or__ definition.

snejus
  • 43
  • 1
  • 4
0

To provide another answer based on the comments that I should be return instances of Foo

class Foo(object):
    def __init__(self, name):
        self.name = name      

    def __invert__(self):
        return Foo({'not': self.name})

    def __repr__(self):
        return str(self.name)

    def __or__(self, other):
        return Foo({'or': [self.name, other]})

    def __and__(self, other):
        return Foo({'and': [self.name, other]})   


x = Foo('obj1')
y = Foo('obj2')

With this I can do:

>>> operator.or_(~x, y) # ~x | y
{'or': [{'not': 'obj1'}, 'obj2']}

And even

>>> operator.or_(~x, ~y) # ~x | ~y
{'or': [{'not': 'obj1'}, {'not': 'obj2'}]}

Mattijn
  • 12,975
  • 15
  • 45
  • 68