David Crook David Crook - 3 months ago 12
Python Question

Functional Infix Implementation in Python

I have a current implementation like this:

class Infix(object):
def __init__(self, func):
self.func = func
def __or__(self, other):
return self.func(other)
def __ror__(self, other):
return Infix(partial(self.func, other))
def __call__(self, v1, v2):
return self.func(v1, v2)

@Infix
def Map(data, func):
return list(map(func,data))


This is great, it works as expected, however I want to also expand this implementation to allow for a left side only solution. If somebody can showcase a solution AND explanation, that would be phenomenal.

Here is an example of what I would like to do...

valLabels['annotations'] \
|Map| (lambda x: x['category_id']) \
|Unique|


Where Unique is defined as below...

@Infix
def Unique(data):
return set(data)


Thanks!

Answer

If you don't mind dropping the final |, you could do

class InfixR(object):
  def __init__(self, func):
    self.func = func
  def __ror__(self, other):
    return self.func(other)
  def __call__(self, v1):
    return self.func(v1)

@InfixR
def Unique(data):
  return set(data)

Then your expression would look like

valLabels['annotations'] \
    |Map| (lambda x: x['category_id']) \
    |Unique

Your original Infix class is (technically) abusing the bitwise or operator: |Map| is nothing special, it's just value | Map | my_lambda, a "bitwise or" of a list, an object, and a lambda, with a couple of spaces removed, and some newlines inserted (using \ to prevent the interpretter from trying to treat each line separately).

In custom classes, you can implement many of the usual operators using __double_underscore__ methods, in the case of bitwise or, they are __or__ and __ror__.

When the python interpreter encounters the | operator, it first looks at the object to the right, to see if it has a __or__ method. It then calls left.__or__(right). If that is not defined or returns NotImplemented, it looks at the object on the right, for __ror__ (reversed or), and calls right.__ror__(left).

The other part of this is the decorator notation.

When you say

@Infix
def Unique(data):
    return set(data)

The interpretter expands that into

def Unique(data):
    return set(data)

Unique = Infix(Unique)

So you get an Infix instance, which has __or__ and __ror__ methods, and a __call__ method. As you might guess, my_obj.__call__() is called when you invoke my_oby().