Python: Defining My Own Operators

Python: defining my own operators?

No, you can't create new operators. However, if you are just evaluating expressions, you could process the string yourself and calculate the results of the new operators.

Is it possible to define a new mathematical operator in python?

You can use one of the predefined operator that has not been implemented for integers.

For example this one : @ (matmul)

You cannot use ! character as it is not in this list.

class MyInt(int):
def __matmul__(self, other):
return self / (self + other)

Then you can do :

a = MyInt(1)
b = MyInt(2)
print(a @ b)

Will print (0.33333333)

How do I define my own operators in TensorFlow

As the comment suggested, there is a how-to guide for adding an op to TensorFlow. This guide covers adding a new op that is implemented in C++. In general, you should do this in the following situations:

  • The op cannot be implemented using existing TensorFlow ops (for example, l1_loss could be implemented using the existing element-wise and reduction operators as a Python function).
  • A C++ implementation is necessary for performance (or memory consumption) reasons.
  • The op could be implemented as a composition of ops, but it has a gradient that can be computed more efficiently (or with better numerical stability) than computing the gradients op-by-op. (This is why tf.nn.l2_loss is implemented as a fused op in C++.)

New operators in Python

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)

Custom Search menu to add my own operators?

I found the answer with some help. Pretty much you need to use:

invoke_search_popup

You'll need to create an enum on an operator then run the invoke_search_popup on the operator.

Here is the example code from Gorgious, and Yilmazz:
https://blender.stackexchange.com/questions/247695/invoke-search-popup-for-a-simple-panel

import bpy
import re
import json


items = (("Metal", "Metal", ""),
("Plastic", "Plastic", ""),
("Glass", "Glass", ""),
("Shadow", "Shadow", ""),
)

PROPS = [
('material', bpy.props.PointerProperty(type=bpy.types.Material, name='Material')),
]

# == OPERATORS
class MYCAT_OT_search_popup(bpy.types.Operator):
bl_idname = "object.search_popup"
bl_label = "Material Renamer"
bl_property = "my_enum"

my_enum: bpy.props.EnumProperty(items = items, name='New Name', default=None)

@classmethod
def poll(cls, context):
return context.scene.material # This prevents executing the operator if we didn't select a material

def execute(self, context):
material = context.scene.material
material.name = self.my_enum
return {'FINISHED'}

def invoke(self, context, event):
wm = context.window_manager
wm.invoke_search_popup(self)
return {'FINISHED'}

# == PANELS
class ObjectRenamerPanel(bpy.types.Panel):

bl_idname = 'VIEW3D_PT_object_renamer'
bl_label = 'Material Renamer'
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'


def draw(self, context):
col = self.layout.column()
row = col.row()
row.prop(context.scene, "material")

col.operator('object.search_popup', text='Rename') #Display the search popup operator

# == MAIN ROUTINE
CLASSES = [
MYCAT_OT_search_popup,
ObjectRenamerPanel,

]

def register():
for (prop_name, prop_value) in PROPS:
setattr(bpy.types.Scene, prop_name, prop_value)

for klass in CLASSES:
bpy.utils.register_class(klass)

def unregister():
for (prop_name, _) in PROPS:
delattr(bpy.types.Scene, prop_name)

for klass in CLASSES:
bpy.utils.unregister_class(klass)


if __name__ == '__main__':
register()

Search Menu

What is programming language support define your own custom operator?

Neither C++ nor Python (C++, Python) support the overloading of "new" operators (although C++ does support overloading the new operator). Overloading operators is just syntactic sugar though - you can always just define a member function, name it something that explains its behaviour better than a single character operator could, and call it instead.

There do exist languages where this is possible: for example Haskell, a beautiful language that does allow you to define your own operators, by using symbols for the name and surrounding them with parentheses, eg:

(!!!) :: [a] -> Integer -> a
(x:xl) !!! 0 = x
(_:xl) !!! n = xl !!! (n-1)
_ !!! _ = error "Index out of bounds"

(note this is just a redefinition of the standard !! function, I'm unimaginative...)

Python: defining my own operators?

No, you can't create new operators. However, if you are just evaluating expressions, you could process the string yourself and calculate the results of the new operators.



Related Topics



Leave a reply



Submit