头图

介绍
此 PEP 描述了向 Python 添加新运算符的建议,这些运算符很有用 用于区分元素和对象操作,并总结 新闻组 comp.lang.python 中关于此主题的讨论。请参阅 Credits 和 末尾的存档部分。这里讨论的问题包括:

背景。
对建议的运算符和实施问题的描述。
分析新运营商的替代方案。
分析替代形式。
兼容性问题
描述更广泛的扩展和其他相关想法。
这个 PEP 的很大一部分描述了没有进入 拟议的延期。之所以呈现它们,是因为扩展本质上是 句法糖,因此必须权衡各种可能的采用 选择。虽然许多替代方案在某些方面可能更好,但 目前的提案似乎总体上是有利的。

有关元素-对象操作的问题扩展到更广泛的领域 比数值计算。本文档还介绍了当前 提案可以与更一般的未来扩展相结合。

背景
Python 提供了 6 个二进制中缀数学运算符:以下一般用 表示。它们可能会过载 为用户定义的类提供了新的语义。但是,对于由以下对象组成的对象 齐次元素,例如数值中的数组、向量和矩阵 计算中,语义有两种本质上截然不同的风格。这 对象操作将这些对象视为多维空间中的点。 元素运算将它们视为单个元素的集合。 这两种操作经常混杂在同一个公式中, 因此需要句法上的区分。+-/%*op

许多数值计算语言提供两组数学运算符。为 例如,在 MatLab 中,ordinary 用于对象操作,而 ordinary 用于元素操作。在 R 中,代表 elementwise operation while 代表对象操作。op.opop%op%

在 Python 中,还有其他表示方法,其中一些已经 由可用的数值包使用,例如:

函数:mul(a,b)
方法:A.mul(B)
演员: a.E*b
在几个方面,这些不如中缀运算符足够。更多详情 稍后会显示,但关键点是:

可读性:即使对于中等复杂的公式,中缀运算符也是 比替代品干净得多。
熟悉度:用户熟悉普通的数学运算符。
实现:新的中缀运算符不会过度混乱 Python 语法。 它们将大大简化数值包的实现。
虽然可以将当前的数学运算符分配给一种风格 语义上,根本没有足够的中缀运算符来重载另一个 味道。这两者之间也不可能保持视觉对称性 如果其中一个不包含普通数学运算符的符号,则为风味。

拟议的延期
六个新的二进制中缀运算符是 添加到核心 Python 中。它们与现有运算符并行。~+~-~~/~%~+-/%
核心 Python 中添加了 6 个增强的赋值运算符。它们与 Python 2.0 中可用的运算符并行。~+=~-=~=~/=~%=~=+=-==/=%==
Operator 保留了 operator 的语法属性, 包括优先级。~opop
Operator 保留 operator 的语义属性 内置数字类型。~opop
运算符引发非数字内置类型的语法错误。这是 在可以就适当的行为达成一致之前是暂时的。~op
这些运算符在名称前置 t 的类中是可重载的(对于 波浪号)到普通数学运算符的名称。例如,和 work for just as 和 work for 。__tadd____rtadd__~+__add____radd__+
与现有运算符一样,当 左操作数不提供适当的方法。__r*__()
它旨在将一组 or 用于 elementwise 操作,另一个用于对象操作,但没有指定哪个 Version of Operators 代表 Elementwise 或 Objectwise 操作,离开 申请的决定。op~op

建议的实现是修补与 分词器、解析器、语法和编译器来复制 必要时对应现有运算符。所有新的语义都是 在重载它们的类中实现。

该符号已在 Python 中用作一元按位 not 运算符。 目前不允许使用二进制运算符。新的运营商是 完全向后兼容。~

原型实现
Greg Lielens 将中缀作为针对 Python 2.0b1 的补丁实现 ~op

为了允许成为二进制运算符的一部分,分词器将被视为一个令牌。这意味着当前有效的表达式将是 标记化为而不是 .然后,解析器会将其视为 的复合。该效果对应用程序不可见。~~+~+1~+1~ + 1~+~ +

关于当前补丁的注意事项:

它还不包括运算符。~op=
行为与列表上的行为相同,而不是提升 异常。~opop
当该提案的最终版本准备就绪时,应修复这些问题。

它保留为中缀运算符,其语义等效于:xor
def __xor__(a, b):

if not b: return a
elif not a: return b
else: 0

这样可以尽可能地保留真实值,否则保留左手 如果可能,侧值。

这样做是为了将按位运算符视为元素运算符 未来的逻辑运算符(见下文)。

添加新运算符的替代方法
关于 comp.lang.python 和 python-dev 邮件列表的讨论探讨了许多 选择。这里列出了一些主要的替代方案,使用 以乘法运算符为例。

使用函数。mul(a,b)
优势:
缺点:
对于复合公式,前缀形式很麻烦。
目标用户不熟悉。
对于目标用户来说太冗长了。
无法使用自然优先级规则。
无需新的操作员。
使用方法调用。a.mul(b)
优势:
缺点:
两个操作数都是不对称的。
目标用户不熟悉。
对于目标用户来说太冗长了。
无法使用自然优先级规则。
无需新的操作员。
使用阴影类。对于矩阵类,定义一个阴影数组类 通过方法 可访问,因此对于矩阵 A 和 B,将是一个矩阵对象,即 。.Ea.E*belementwise_mul(a,b)
同样,为可通过方法访问的数组定义一个影子矩阵类,以便对于数组 a 和 b,数组为 。.Ma.M*bmatrixwise_mul(a,b)
优势:
缺点:
在当前的 Python 中很难维护,因为普通数字不能有 用户定义的类方法;即 如果 a 是纯的,则会失败 数。a.E*b
难以实现,因为这会干扰现有的方法调用, 比如转置等。.T
对象创建和方法查找的运行时开销。
影子类不能替换真正的类,因为它不能 返回自己的类型。所以需要有一个带有影子类的类,以及一个带有影子类的类。MEEM
对数学家来说是不自然的。
无需新的操作员。
具有正确优先级规则的中缀运算符的优点。
在应用程序中清洁配方。
实现矩阵类和元素类,并轻松转换为其他类 类。因此,数组的矩阵运算类似于 矩阵的元素运算如下。对于错误 检测将引发异常。a.Mb.Ma.Eb.Ea.E*b.M
优势:
缺点:
由于缺乏纯数字的用户方法,类似的困难。
对象创建和方法查找的运行时开销。
更杂乱的公式。
为方便操作员而切换对象的风格变得持久。 这在应用程序代码中引入了长程上下文依赖关系,这些依赖关系 将非常难以维护。
无需新的操作员。
类似于具有正确优先级规则的中缀表示法。
使用微型解析器解析以任意扩展名编写的公式,放置在 带引号的字符串。
优势:
缺点:
实际语法在带引号的字符串中,这不能解析 问题本身。
引入特殊语法区域。
对迷你解析器的要求很高。
纯 Python,没有新的运算符
引入单个运算符,例如 ,用于矩阵乘法。@
优势:
缺点:
像这样的运营商的区别是相同的 重要。它们在矩阵或面向数组的包中的含义是 反转(见下文)。+-**
新运算符具有特殊字符。
这不适用于更一般的对象元素问题。
引入更少的运算符
在这些备选方案中,第一种和第二种用于当前应用 在某种程度上,但发现不足。第三个是最受欢迎的 应用程序,但它会产生巨大的实现复杂性。第四个 会使应用程序代码对上下文非常敏感且难以维护。 由于 到当前类型/类拆分。第五个似乎比它造成的问题更多 会解决。第六种没有涵盖相同的应用范围。

中缀运算符的替代形式
新中缀运算符的两种主要形式和几种次要变体是 讨论:

括号内形式
(op)
[op]
{op}
<op>
:op:
~op~
%op%
元字符形式:
.op
@op
~op

或者,将元字符放在运算符之后。
这些主题的变体不太一致。这些都被考虑在内 不利。为了完整起见,这里列出了一些:
用于左右除法@//@
用于外层和内层产品*
使用单个运算符进行乘法运算。@
用于模拟乘法:__call__
a(b) or (a)(b)
在表示中进行选择的标准包括:

与现有运算符没有语法歧义。
在实际公式中具有更高的可读性。这使得括号内的形式 不利。请参阅以下示例。
在视觉上类似于现有的数学运算符。
语法简单,不会阻止未来可能的扩展。
根据这些标准,括号形式的总冠军似乎是 。 元字符形式的明显赢家是 。比较这些吧 似乎是其中的最爱。{op}~op~op

部分分析如下:

形式不明确:将不同于 。.op1.+a1.+a
支架式运算符在单独站立时最有利,但 不在公式中,因为它们会干扰括号的视觉解析 precedence 和 function 参数。对于和 ,和 都是如此 对于 和 .(op)[op]{op}<op>
该形式有可能与 和 混淆。<op><>=
不被看好,因为视觉上很重(密集,更像是 一个字母):比 . 更容易读作 。@op@a@+ba@+ba@+b
对于选择元字符:大多数现有的 ASCII 符号已经 被使用过。唯一未使用的三个是 .@$?

新运算符的语义
使用任何一组运算符作为对象都有令人信服的论据 或元素。其中一些列在这里:

opfor 元素, for object~op
与当前 Numeric 包的多阵列接口一致。
与其他一些语言一致。
认为元素操作更自然。
认为元素操作的使用频率更高
opfor object, for element~op
与当前 MatPy 封装的线性代数接口一致。
与其他一些语言一致。
认为对象操作更自然。
感知对象操作的使用频率更高。
与列表中运算符的当前行为一致。
允许将来成为一般的元素元字符 扩展。~
人们普遍认为

没有绝对的理由偏袒其中之一。
很容易在相当大的块中从一个表示形式转换为另一个表示形式 代码,所以运算符的另一种风格总是少数。
还有其他语义差异有利于面向数组的存在 和面向矩阵的包,即使它们的运算符是统一的。
无论做出什么决定,使用现有接口的代码都不应该 坏了很长一段时间。
因此,如果语义 这两组运算符的风格不是由核心语言决定的。 应用程序包负责做出最合适的选择。 NumPy 和 MatPy 已经是这种情况,它们使用相反的语义。 添加新的运算符不会破坏这一点。另请参阅观察后 以下示例中的第 2 小节。

提出了数值精度的问题,但如果语义留给 应用,实际精度也应该去那里。

例子
以下是将使用各种 运算符或上述其他表示形式。

矩阵反演公式:
观察:对于线性代数,最好使用 for object。op
观察结果:类型运算符看起来比键入更好 复杂的公式。~op(op)
观察结果:命名运算符不适合数学公式。
使用命名运算符:
b = a.I @sub a.I @mul u @div (c.I @add v @div a @mul u) @mul v @div a
b = a.I ~sub a.I ~mul u ~div (c.I ~add v ~div a ~mul u) ~mul v ~div a
使用 for object 和 for element:op~op
b = a.I @- a.I @ u @/ (c.I @+ v@/a@u) @* v @/ a
b = a.I ~- a.I ~ u ~/ (c.I ~+ v~/a~u) ~* v ~/ a
b = a.I (-) a.I () u (/) (c.I (+) v(/)a()u) (*) v (/) a
b = a.I [-] a.I [] u [/] (c.I [+] v[/]a[]u) [*] v [/] a
b = a.I <-> a.I <> u </> (c.I <+> v</>a<>u) <*> v </> a
b = a.I {-} a.I {} u {/} (c.I {+} v{/}a{}u) {*} v {/} a
使用 for element 和 for object:op~op
b = a.I - a.I u / (c.I + v/au) * v / a
b = a.I - a.I u (c.I + va.Iu).I v a.I

绘制 3D 图形
观察:广播的元素操作允许更多 比 MatLab 更高效的实施。
观察:有两个语义为 和 交换的相关类很有用。使用这些,操作员只会 需要出现在其他风格占主导地位的代码块中,而 保持代码语义的一致性。op~op~op
使用 for object 和 for element:op~op
z = sin(x~2 ~+ y~2); plot(x,y,z)
对元素使用 op,对对象使用 ~op:
z = sin(x2 + y2); plot(x,y,z)
使用和自动广播:+-
a = b - c; d = a.T*a

观察:如果 b 或 c 中的一个是行向量,而另一个是列向量,这将默默地产生难以跟踪的错误。

杂项问题
需要操作员。从对象上讲,它们是 之所以重要,是因为它们根据线性代数提供重要的健全性检查。 元素很重要,因为它们允许广播 在应用程序中非常有效。~+~-+-+-
左除法(求解)。对于矩阵,不一定等于 。因此,的解 表示为 , 与 的解不同,表示 。有 关于寻找 SOLVE 新符号的讨论。[背景:MatLab 的 for 和 for 用途。axxaax==bx=solve(a,b)xa==bx=div(b,a)b/adiv(b,a)a\bsolve(a,b)
众所周知,Python 提供了更好的解决方案,而无需 新符号:可以使该方法被延迟,以便 和 等价于 Matlab 的 和 。这 实现非常简单,生成的应用程序代码干净。inverse.Ia.Ibba.Ia\bb/a
电源操作员。Python 对 as 的使用有两个感知 弊:a**bpow(a,b)
但是,此问题与此处的主要问题不同。
大多数数学家对此更熟悉。a^b
它会导致长增强赋值运算符。~**=
其他乘法运算符。乘法的几种形式是 用于(多)线性代数。大多数可以看作是 线性代数意义上的乘法(例如 Kronecker 积)。但是两个 形式似乎更基本:外在产品和内产品。 但是,它们的规范包括索引,可以是
后者(爱因斯坦符号)在纸上被广泛使用,也 更容易实现的。通过实现 tensor-with-indices 类,一个 乘法的一般形式将涵盖外积和内积,并且 也专攻线性代数乘法。索引规则可以是 定义为类方法,例如:
a = b.i(1,2,-1,-2) * c.i(4,-2,3,-1) # a_ijkl = b_ijmn c_lnkm

因此,一个对象乘法就足够了。
与运算符关联,或
与对象关联。
按位运算符。
提出的新数学运算符使用符号 ~,即按位而不是运算符。这不会造成兼容性问题,但会有些复杂 实现。
该符号可能比按位使用更好。但 这取决于按位运算符的未来。它不会立即 对建议的数学运算符的影响。^powxor
建议将该符号用于矩阵求解。但是新的 使用延迟的解决方案在几个方面更好。|.I
目前的提案适合一个更大、更普遍的扩展,它将 无需特殊的按位运算符。(请参阅下面的元素化。
定义中使用的特殊运算符名称的替代方法,
def "+"(a, b) in place of def __add__(a, b)

这似乎需要更多的语法更改,并且只会有用 当允许任意附加运算符时。

对一般元素化的影响
对象操作和元素操作之间的区别在 在其他上下文中,对象在概念上可以被视为 元素的集合。重要的是,目前的提案没有 排除未来可能的延期。

一个通用的未来扩展是用作元运算符来元素化给定的运算符。下面列出了几个示例:~

按位运算符。目前,Python 为按位分配了六个运算符 运算:和 () 或 ()、xor ()、补码 ()、左 shift () 和右移 (),具有自己的优先级。&|^~<<>>
其中,运营商可以看作是 适用于整数的格运算符的元素版本 位字符串:&|^~
5 and 6 # 6
5 or 6 # 5

5 ~and 6 # 4
5 ~or 6 # 7

这些可以看作是一般的元素格算子,而不是 仅限于整数中的位。
为了具有 的命名运算符,必须 做一个保留的词。xor~xorxor
列出算术。
[1, 2] + [3, 4] # [1, 2, 3, 4]
[1, 2] ~+ [3, 4] # [4, 6]

['a', 'b'] * 2 # ['a', 'b', 'a', 'b']
'ab' * 2 # 'abab'

['a', 'b'] ~* 2 # ['aa', 'bb']
[1, 2] ~* 2 # [2, 4]

它也与笛卡尔积一致:
[1,2]*[3,4] # [(1,3),(1,4),(2,3),(2,4)]
列表推导。
a = [1, 2]; b = [3, 4]
~f(a,b) # [f(x,y) for x, y in zip(a,b)]
~f(a*b) # [f(x,y) for x in a for y in b]
a ~+ b # [x + y for x, y in zip(a,b)]
元组生成(Python 2.0 中的 zip 函数):
[1, 2, 3], [4, 5, 6] # ([1,2, 3], [4, 5, 6])
[1, 2, 3]~,[4, 5, 6] # [(1,4), (2, 5), (3,6)]
使用通用元素元字符替换 map:~
~f(a, b) # map(f, a, b)~
~f(a, b) # map(lambda x:map(f, x), a, b)

更一般地说,:
def ~f(x): return map(f, x)
def ~~f(x): return map(~f, x)
...
Elementwise 格式运算符(带广播):
a = [1,2,3,4,5]
print ["%5d "] ~% a
a = [[1,2],[3,4]]
print ["%5d "] ~~% a
丰富的对比:
[1, 2, 3] ~< [3, 2, 1] # [1, 0, 0]
[1, 2, 3] ~== [3, 2, 1] # [0, 1, 0]
丰富的索引:
[a, b, c, d] ~[2, 3, 1] # [c, d, b]
元组扁平化:
a = (1,2); b = (3,4)
f(~a, ~b) # f(1,2,3,4)
复制运算符:
a ~= b # a = b.copy()
可以有特定级别的深拷贝:
a ~~= b # a = b.copy(2)

笔记
可能还有很多其他类似的情况。这种一般方法 似乎非常适合其中的大多数,而不是几个单独的扩展 对于它们中的每一个(并行和交叉迭代,列表推导,丰富 比较等)。
elementwise 的语义取决于应用程序。例如,一个 矩阵的元素从列表的角度向下两个级别。 这需要比目前的提案更根本的改变。在任何 在这种情况下,目前的提案不会对未来产生负面影响 这种性质的可能性。
请注意,本部分介绍一种一致的未来扩展类型 与当前提案,但可能提供额外的兼容性或其他 问题。它们与当前提案无关。

对指定运算符的影响
讨论普遍表明,中缀运算符是稀缺的 Python中的资源,不仅在数值计算中,而且在其他领域 井。提出了一些允许中缀的建议和想法 运算符的引入方式类似于命名函数。我们在这里表明 当前的扩展不会对未来的扩展产生负面影响 方面。

命名中缀运算符。
选择一个元字符,比如 ,这样对于任何标识符 , 该组合将是一个二进制中缀运算符,并且:@opname@opname
a @opname b == opname(a,b)

提到的其他表示包括:
.name ~name~ :name: (.name) %name%

和类似的变化。不能使用基于纯括号的运算符 这边。
这需要在解析器中进行更改才能识别并解析它 与函数调用相同的结构。所有这些的优先权 运算符必须固定在一个级别,因此实现将 与保持优先级的附加数学运算符不同 现有的数学运算符。@opname
目前拟议的延期并不限制未来可能的延期 以任何方式以这种形式。
更通用的符号运算符。
未来扩展的另一种形式是使用 meta 字符和 运算符符号(不能在语法结构中使用的符号 运算符除外)。假设是元字符。然后:@
a + b, a @+ b, a @@+ b, a @+- b

都是具有优先级层次结构的运算符,定义如下:
def "+"(a, b)
def "@+"(a, b)
def "@@+"(a, b)
def "@+-"(a, b)

与命名运算符相比,一个优势是更大的灵活性 基于元字符或普通运算符的优先级 符号。这也允许操作员组合。缺点是 它们更像是线路噪声。无论如何,目前的提案没有 影响其未来的可能性。
当 Unicode 成为 已正式发布。
请注意,本节讨论建议的扩展的兼容性 以及未来可能的扩展。这些的可取性或兼容性 此处未特别考虑其他扩展本身。
原文地址:https://www.bilibili.com/read/readlist/rl812939


隐官_陈
1 声望0 粉丝