There is a not-so-uncommon cornercase in which the class-oriented operator concept yields unwanted results. The situation is as follows:
Now, consider the following expression:
A() + B()
The following happens:
A() checks if it can operate with B().
The elements of A() cannot deal with B(), since B() is in fact an array.
So the reverse operation on B() is invoked, with each of the elements of A().
B() applies the scalar elements of A() to each of its elements in turn.
One ends up with the following situation:
. A() + B() = A() + B()
+---------------------+--------------------+ +----------------------------+-----------------------------+
| | | | | |
+---+---+ | +---+---+ | +---+---+ | | +---------+---------+ | +---------+---------+ |
+---+---+ | a | b | | | a | b | | | a | b | | | | 1 + a | 1 + b | | | 2 + a | 2 + b | |
| 1 | 2 | + +---+---+ = | 1 + +---+---+ | 2 + +---+---+ | = | +---------+---------+ | +---------+---------+ |
+---+---+ | c | d | | | c | d | | | c | d | | | | 1 + c | 1 + d | | | 2 + d | 2 + d | |
+---+---+ | +---+---+ | +---+---+ | | +---------+---------+ | +---------+---------+ |
| | | | | |
+---------------------+--------------------+ +----------------------------+-----------------------------+
Expected (wanted) is the broadcast of A() to the shape of B() and an element-wise operation + on each pair. This is what happens on B() + A():
. B() + A() = B() + A()
+---+---+ +---+---+ +---+---+ +---------+---------+
| a | b | +---+---+ | a | b | | 1 | 1 | | a + 1 | b + 1 |
+---+---+ + | 1 | 2 | = +---+---+ + +---+---+ = +---------+---------+
| c | d | +---+---+ | c | d | | 2 | 2 | | c + 2 | d + 2 |
+---+---+ +---+---+ +---+---+ +---------+---------+
Thus the ignorance of A on the probably later introduced class B leads to an unexcepted result.
Fixing this problem is not easy. When one has usually no access anymore to the implementation of A, it seems even impossible. If one still has access to some hooks in the implementation of A, one can monkey-patch A, but this is error-prone and little portable to scenarios where many packages want to access A‘s hooks. Especially for the numpy case, this monkey-patching is possible, although it lets the application currently crash on exit (April 2011, numpy 1.5.1).
priops encourages all packages involved (here those of A and B) to give their power of deciding what to do away, to the Priop defined in the priops package. There are as many Priops for as many types of operation as needed. Priops consists of a description of the possible operations, optionally together with some cost measure (which might be boolean, for simplicity).
When one wants to use the priop, one retrieves a kind of handle for the priop, defining the search criteria. This handle’s function is to find a path of operations using the information stored in the Priop once the specification of the operation are known (classes of objects, output type ...).
There exists a common data format for operator information exchange between concatenated operations used to carry out the request, and there exists a framework implementing the graph notions used.
The priops approach can be localised to a non-global usecase by instantiating the classes provided by priops in the namespace where they are needed.