6

I'm writing a python program in which I need to overload the >> operator. The problem that I'm facing is that this operator need to be right associative. So if I do the following

A >> B >> C >> D >> E

I want this to be parsed as

(A >> (B >> (C >> (D >> E))))

From what I understand, in python this operator is left associative and so I would get,

((((A >> B) >> C) >> D) >> E)

Is there anyway to change the default associativity of an operator in python when you do operator overloading?

6
  • I may be wrong but isnt this not an issue with operator overloading but more just standard "order of operations"? If they are all the same operator then it would eval left to right Commented May 8, 2012 at 0:57
  • overload the << operator intead ;) Commented May 8, 2012 at 1:09
  • @wim I thought of that, haha. It's just that it would be much more natural for anyone used to reading from left to right to do it the other way. I also thought of overloading the = operator as I think that's right associative but that would just be horifficly ugly. Commented May 8, 2012 at 1:16
  • What's the use case for this? Overloading standard operators to do magical things is almost always a bad idea, as it makes the code hard for other people (or you in six months time) to figure out and maintain. Consider def func(*args): ... and func(A, B, C, D, E). Commented May 8, 2012 at 1:53
  • 1
    Fair enough. Though you might still be better off with just a KISS, well-named function. Python isn't really good for creating custom DSLs within Python itself, as the grammar is hard to extend. I believe you can play with the abstract syntax tree, but it's messy. Commented May 8, 2012 at 2:07

2 Answers 2

4

This can be done...but it will require a bit of effort. The basic idea is to use the right shift operator to create and update a new type of object that defers the actual computation.

For instance, let's say that your variables above: A, B, C, D, and E are all objects of type Actual. We'll introduce a new class Deferred that is produced by the rshift operation on an instance of Actual. Deferred also implements the rshift operator, which updates the object and returns itself.

(BTW, For the remainder of this answer, I'm assuming that A, B, C, D, and E are immutable and that the rshift operation produces a new object.)

F = A >> B >> C >> D >> E

Would be computed like ...

F = Deferred(A,B) >> C >> D >> E
F = Deferred(A,B,C) >> D >> E
F = Deferred(A,B,C,D) >> E
F = Deferred(A,B,C,D,E)

F maintains a cached instance of Actual, that is computed from the reverse sequence. Furthermore, F implements the same interface as Actual, so that methods invoked on an instance of Deferred are delegated to the cached instance of Actual.

I don't know the kind of computation you're doing, so in the following example, I make up something kind of trivial, just to demonstrate that the when the deferred computation is actually performed, they are reversed.

class Var(object):
    def __init__(self):
        pass

    @property
    def name(self):
        return self._name( )

    @property
    def length(self):
        return len(self.name)


class Actual(Var):
    def __init__(self, name):
        Var.__init__(self)
        self._text = name

    def _name(self):
        return self._text

    def __rshift__(self, other):
        if isinstance(other, Actual):
            return Deferred(self, other)

        return len(self.name)

    @staticmethod
    def NewFromShiftComputation(sequence):
        x = ' >> '.join(reversed(map(lambda actual: actual.name, sequence)))
        return Actual(x)



class Deferred(Var):
    def __init__(self, *args):
        Var.__init__(self)

        self._items  = [ ]
        self._actual = None  #-- cached "actual"

        for item in args:
            self._items.append(item)

    def _name(self):
        self._assure_actual( )
        return self._actual.name

    def __rshift__(self, other):
        self._actual = None  #-- Invalidate the cached "actual"
        self._items.append(other)
        return self

    def _assure_actual(self):
        if self._actual is None:
            self._actual = Actual.NewFromShiftComputation(self._items)



A = Actual('A')
B = Actual('B')
C = Actual('C')
D = Actual('D')
E = Actual('E')

F = A >> B >> C >> D >> E

print F.name
print F.length
Sign up to request clarification or add additional context in comments.

Comments

1

This isnt an issue with the operator. Python evaluates left to right:
http://docs.python.org/reference/expressions.html#evaluation-order

So, parenthesis would be needed in this case since its all the same operator.

2 Comments

I was afraid of that. I was just hoping that there was some way you could tell the python interpreter to alter the associativity of an operator in certain cases.
@martega: yea that might break other libs you are using if you could change the rules of the interp. why not just make a function that takes them as args and runs the correct order of ops?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.