Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python operator overloading and operator associativity

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?

like image 600
martega Avatar asked May 08 '12 00:05

martega


2 Answers

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
like image 136
parselmouth Avatar answered Sep 16 '22 16:09

parselmouth


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.

like image 20
jdi Avatar answered Sep 16 '22 16:09

jdi