我们可以定义 Python 的内在运算符,如此处所述。只是出于好奇,我们可以定义新的运算符,如$
or***
吗?(如果是这样,那么我们可以定义三元条件运算符或旋转运算符。)
4 回答
正如@minitech 所说,您不能定义新的运算符。但是检查这个允许您定义中缀运算符的 hack http://code.activestate.com/recipes/384122-infix-operators/
扩展@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)
不,您不能在 Python 中定义新的运算符。
是的,您可以做到,您可以添加和/或更改关键字、修改语法以及您想要的任何其他内容。
它的 Python,你可以为所欲为,你只受你的想象力的限制。
有效的方法当然是修改解释器并编译它以包含您的运算符。但是,由于我们希望动态添加它们,您要做的是:
您创建一个在程序顶部导入的模块,该模块拦截其余代码的执行,而不是重新分析您的代码,进行必要的更改以使 Python 可以理解您的新语法,然后执行更改的版本而不是您的程序。
如果您希望它仅在脚本上工作,那并不难。您可以通过搜索和替换文本代码来做到这一点,将运算符替换为模块具有的函数版本,然后使用 compile() 编译正确的代码并将解释器指向该代码对象。
这样做的一个例子是 goto 笑话,它是一个向 Python 添加 goto 功能的模块,因此请看一下如何将更改的代码动态地注入程序流中。
如果您还想在字节编译文件中支持您的新运算符,那么这有点困难。您将不得不使用 ast 模块将您的运算符添加到抽象语法树中,描述它如何使用预编译的代码序列或其他技巧将其转换为字节码,然后将 AST 传递给 compile() 函数并强制它转换为字节码。
还有一个回溯问题,即当操作数出现问题时,您希望如何呈现异常。您必须编写与代码中的行相对应的正确行号,而不是实际被解释的行号。
这里真正的问题不是它是否可以做到,而是它是否负担得起。如果它对你很重要,你会按照我的方式去做,如果你想避免头疼,你将使用中间对象作为运算符,如其他答案中所述。如果它对您非常重要,那么您将着手对 Python 本身进行更改,但是,您的代码在任何其他 Python 解释器上都将毫无用处。