7

我们可以定义 Python 的内在运算符,如此处所述。只是出于好奇,我们可以定义新的运算符,如$or***吗?(如果是这样,那么我们可以定义三元条件运算符或旋转运算符。)

4

4 回答 4

7

正如@minitech 所说,您不能定义新的运算符。但是检查这个允许您定义中缀运算符的 hack http://code.activestate.com/recipes/384122-infix-operators/

于 2013-10-15T04:39:38.713 回答
4

扩展@fasouto 答案,但添加更多代码。

虽然您不能定义新的运算符,也不能为内置类型重新定义现有的运算符,但您可以做的是定义一个类(实例化为任何有效的 Python 名称,例如op)充当两个对象的中间绑定,从而有效地查找像二进制中缀运算符:

a | op | b

非约束性实施

简而言之,可以为运算符定义一个覆盖前向后向方法的类,例如__or____ror__对于|运算符:

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)

这个可以直接使用:

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

1 | op | 2
# 3

或作为装饰者:

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

1 | op | 2
# 3

上述解决方案按原样工作,但存在一些问题,例如op | 2不能单独使用表达式

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

绑定实现

要获得正确的绑定,需要编写一些更复杂的代码来执行中间绑定

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)

这与以前的使用方式相同,例如:

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

或作为装饰者:

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

有了这个,一个人会得到:

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

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

1 | op | 2
# 3

还有一个 PyPI 包基本上实现了这个:https ://pypi.org/project/infix/

计时

顺便说一句,绑定解决方案似乎也稍微快一点:

%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

笔记

这些实现使用|,但可以使用任何二元运算符:

  • +__add__
  • -__sub__
  • *__mul__
  • /__truediv__
  • //__floordiv__
  • %__mod__
  • **__pow__
  • @: __matmul__(对于 Python 3.5 及以上版本)
  • |__or__
  • &__and__
  • ^__xor__
  • >>__rshift__
  • <<__lshift__

**将需要绑定实现或调整非绑定实现以反映运算符是右关联的。上面的所有其他运算符要么是左结合运算符(-, /, //, %, @, >>, <<),要么是直接交换运算符(+, *, |, &, ^)。

请记住,这些都将具有与普通 Python 运算符相同的优先级,因此,例如:

(1 | op | 2 * 5) == (1 | op | (2 * 5)) != ((1 | op | 2) * 5)
于 2019-06-24T15:38:52.413 回答
3

不,您不能在 Python 中定义新的运算符。

于 2013-10-15T04:20:56.597 回答
-1

是的,您可以做到,您可以添加和/或更改关键字、修改语法以及您想要的任何其他内容。

它的 Python,你可以为所欲为,你只受你的想象力的限制。

有效的方法当然是修改解释器并编译它以包含您的运算符。但是,由于我们希望动态添加它们,您要做的是:

您创建一个在程序顶部导入的模块,该模块拦截其余代码的执行,而不是重新分析您的代码,进行必要的更改以使 Python 可以理解您的新语法,然后执行更改的版本而不是您的程序。

如果您希望它仅在脚本上工作,那并不难。您可以通过搜索和替换文本代码来做到这一点,将运算符替换为模块具有的函数版本,然后使用 compile() 编译正确的代码并将解释器指向该代码对象。

这样做的一个例子是 goto 笑话,它是一个向 Python 添加 goto 功能的模块,因此请看一下如何将更改的代码动态地注入程序流中。

如果您还想在字节编译文件中支持您的新运算符,那么这有点困难。您将不得不使用 ast 模块将您的运算符添加到抽象语法树中,描述它如何使用预编译的代码序列或其他技巧将其转换为字节码,然后将 AST 传递给 compile() 函数并强制它转换为字节码。

还有一个回溯问题,即当操作数出现问题时,您希望如何呈现异常。您必须编写与代码中的行相对应的正确行号,而不是实际被解释的行号。

这里真正的问题不是它是否可以做到,而是它是否负担得起。如果它对你很重要,你会按照我的方式去做,如果你想避免头疼,你将使用中间对象作为运算符,如其他答案中所述。如果它对您非常重要,那么您将着手对 Python 本身进行更改,但是,您的代码在任何其他 Python 解释器上都将毫无用处。

于 2021-03-27T17:06:13.540 回答