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)