Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

New operators in Python

We can define intrinsic operators of Python as stated here. Just for curiosity, can we define new operators like $ or ***? (If so, then we can define ternary condition operators or rotate operators.)

like image 526
hola Avatar asked Oct 15 '13 04:10

hola


3 Answers

As @minitech said you can't define new operators. But check this hack that allows you to define infix operators http://code.activestate.com/recipes/384122-infix-operators/

like image 186
fasouto Avatar answered Sep 27 '22 17:09

fasouto


Expanding on @fasouto answer, but adding a bit more code.

While you cannot define new operators AND you cannot redefine existing operators for built-in types, what you can do is to define a class (instantiated to any valid Python name, e.g. op) that act as an intermediate binding for two objects, thus effectively looking like a binary infix operator:

a | op | b

Non-binding Implementation

In short, one can define a class overriding forward and backward methods for an operator, e.g. __or__ and __ror__ for the | operator:

class Infix:
    def __init__(self, function):
        self.function = function
    def __ror__(self, other):
        return Infix(lambda x, self=self, other=other: self.function(other, x))
    def __or__(self, other):
        return self.function(other)
    def __call__(self, value1, value2):
        return self.function(value1, value2)

This can be used directly:

op = Infix(lambda a, b: a + b)  # can be any bivariate function

1 | op | 2
# 3

or as a decorator:

@Infix
def op(a, b):
    return a + b

1 | op | 2
# 3

The above solution works as is, but there are some issues, e.g. op | 2 expression cannot be used alone:

op = Infix(lambda a, b: a + b)
(1 | op)
#<__main__.Infix object at 0x7facf8f33d30>

# op | 2
# TypeError: <lambda>() missing 1 required positional argument: 'b'

1 | op | 2)
# 3

Binding Implementation

To get proper bindings one would need to write a bit more complex code performing an intermediate binding:

class Infix(object):
    def __init__(self, func):
        self.func = func

    class RBind:
        def __init__(self, func, binded):
            self.func = func
            self.binded = binded
        def __call__(self, other):
            return self.func(other, self.binded)
        __ror__ = __call__

    class LBind:
        def __init__(self, func, binded):
            self.func = func
            self.binded = binded
        def __call__(self, other):
            return self.func(self.binded, other)
        __or__ = __call__

    def __or__(self, other):
        return self.RBind(self.func, other)

    def __ror__(self, other):
        return self.LBind(self.func, other)

    def __call__(self, value1, value2):
        return self.func(value1, value2)

This is used the same way as before, e.g. either:

op = Infix(lambda a, b: a + b)

or as a decorator:

@Infix
def op(a, b):
    return a + b

With this, one would get:

1 | op
# <__main__.Infix.LBind object at 0x7facf8f2b828>

op | 2
# <__main__.Infix.RBind object at 0x7facf8f2be10>

1 | op | 2
# 3

There is also a PyPI package implementing substantially this: https://pypi.org/project/infix/

Timings

Incidentally, the binding solutions seems to be also marginally faster:

%timeit [1 | op | 2 for _ in range(1000)]

# Non-binding implementation
# 1000 loops, best of 3: 626 µs per loop

# Binding implementation
# 1000 loops, best of 3: 525 µs per loop

Notes

These implementations use |, but any binary operator could be used:

  • +: __add__
  • -: __sub__
  • *: __mul__
  • /: __truediv__
  • //: __floordiv__
  • %: __mod__
  • **: __pow__
  • @: __matmul__ (for Python 3.5 onwards)
  • |: __or__
  • &: __and__
  • ^: __xor__
  • >>: __rshift__
  • <<: __lshift__

The ** would require the binding implementation or adjusting the non-binding one to reflect that the operator is right-associative. All the other operators from above are either left-associative (-, /, //, %, @, >>, <<) or directly commutative (+, *, |, &, ^).

Remember that these would all have the same precedence as the normal Python operators, hence, e.g.:

(1 | op | 2 * 5) == (1 | op | (2 * 5)) != ((1 | op | 2) * 5)
like image 30
norok2 Avatar answered Sep 27 '22 17:09

norok2


No, you can’t define new operators in Python.

like image 21
Ry- Avatar answered Sep 27 '22 16:09

Ry-