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.)
Advertisement
Answer
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 (with which I have no affiliation) 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)