PEP 225 – 元素/对象运算符

猫勺猫勺 05-06 92 阅读 0 评论

介绍

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 邮件列表的讨论探讨了许多 选择。这里列出了一些主要的替代方案,使用 以乘法运算符为例。

  1. 使用函数。mul(a,b)

    优势:

    缺点:

    • 对于复合公式,前缀形式很麻烦。

    • 目标用户不熟悉。

    • 对于目标用户来说太冗长了。

    • 无法使用自然优先级规则。

    • 无需新的操作员。

  2. 使用方法调用。a.mul(b)

    优势:

    缺点:

    • 两个操作数都是不对称的。

    • 目标用户不熟悉。

    • 对于目标用户来说太冗长了。

    • 无法使用自然优先级规则。

    • 无需新的操作员。

  3. 使用阴影类。对于矩阵类,定义一个阴影数组类 通过方法 可访问,因此对于矩阵 A 和 B,将是一个矩阵对象,即 。.Ea.E*belementwise_mul(a,b)

    同样,为可通过方法访问的数组定义一个影子矩阵类,以便对于数组 a 和 b,数组为 。.Ma.M*bmatrixwise_mul(a,b)

    优势:

    缺点:

    • 在当前的 Python 中很难维护,因为普通数字不能有 用户定义的类方法;即 如果 a 是纯的,则会失败 数。a.E*b

    • 难以实现,因为这会干扰现有的方法调用, 比如转置等。.T

    • 对象创建和方法查找的运行时开销。

    • 影子类不能替换真正的类,因为它不能 返回自己的类型。所以需要有一个带有影子类的类,以及一个带有影子类的类。MEEM

    • 对数学家来说是不自然的。

    • 无需新的操作员。

    • 具有正确优先级规则的中缀运算符的优点。

    • 在应用程序中清洁配方。

  4. 实现矩阵类和元素类,并轻松转换为其他类 类。因此,数组的矩阵运算类似于 矩阵的元素运算如下。对于错误 检测将引发异常。a.M*b.Ma.E*b.Ea.E*b.M

    优势:

    缺点:

    • 由于缺乏纯数字的用户方法,类似的困难。

    • 对象创建和方法查找的运行时开销。

    • 更杂乱的公式。

    • 为方便操作员而切换对象的风格变得持久。 这在应用程序代码中引入了长程上下文依赖关系,这些依赖关系 将非常难以维护。

    • 无需新的操作员。

    • 类似于具有正确优先级规则的中缀表示法。

  5. 使用微型解析器解析以任意扩展名编写的公式,放置在 带引号的字符串

    优势:

    缺点:

    • 实际语法在带引号的字符串中,这不能解析 问题本身。

    • 引入特殊语法区域。

    • 对迷你解析器的要求很高。

    • 纯 Python,没有新的运算符

  6. 引入单个运算符,例如 ,用于矩阵乘法。@

    优势:

    缺点:

    • 像这样的运营商的区别是相同的 重要。它们在矩阵或面向数组的包中的含义是 反转(见下文)。+-**

    • 新运算符具有特殊字符。

    • 这不适用于更一般的对象元素问题。

    • 引入更少的运算符

在这些备选方案中,第一种和第二种用于当前应用 在某种程度上,但发现不足。第三个是最受欢迎的 应用程序,但它会产生巨大的实现复杂性。第四个 会使应用程序代码对上下文非常敏感且难以维护。 由于 到当前类型/类拆分。第五个似乎比它造成的问题更多 会解决。第六种没有涵盖相同的应用范围。

中缀运算符的替代形式

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

  • 括号内形式

(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 符号已经 被使用过。唯一未使用的三个是 .@$?


新运算符的语义

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

  1. opfor 元素, for object~op

    • 与当前 Numeric 包的多阵列接口一致。

    • 与其他一些语言一致。

    • 认为元素操作更自然。

    • 认为元素操作的使用频率更高

  2. opfor object, for element~op

    • 与当前 MatPy 封装的线性代数接口一致。

    • 与其他一些语言一致。

    • 认为对象操作更自然。

    • 感知对象操作的使用频率更高。

    • 与列表中运算符的当前行为一致。

    • 允许将来成为一般的元素元字符 扩展。~

人们普遍认为

  • 没有绝对的理由偏袒其中之一。

  • 很容易在相当大的块中从一个表示形式转换为另一个表示形式 代码,所以运算符的另一种风格总是少数。

  • 还有其他语义差异有利于面向数组的存在 和面向矩阵的包,即使它们的运算符是统一的。

  • 无论做出什么决定,使用现有接口的代码都不应该 坏了很长一段时间。

因此,如果语义 这两组运算符的风格不是由核心语言决定的。 应用程序包负责做出最合适的选择。 NumPy 和 MatPy 已经是这种情况,它们使用相反的语义。 添加新的运算符不会破坏这一点。另请参阅观察后 以下示例中的第 2 小节。

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

例子

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

  1. 矩阵反演公式:

    观察:对于线性代数,最好使用 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/a*u) * v / a
      b = a.I - a.I * u * (c.I + v*a.I*u).I * v * a.I


  2. 绘制 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(x**2 + y**2);   plot(x,y,z)
  3. 使用和自动广播:+-

    a = b - c;  d = a.T*a

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


杂项问题

  • 需要操作员。从对象上讲,它们是 之所以重要,是因为它们根据线性代数提供重要的健全性检查。 元素很重要,因为它们允许广播 在应用程序中非常有效。~+~-+-+-

  • 左除法(求解)。对于矩阵,不一定等于 。因此,的解 表示为 , 与 的解不同,表示 。有 关于寻找 SOLVE 新符号的讨论。[背景:MatLab 的 for 和 for 用途。a*xx*aa*x==bx=solve(a,b)x*a==bx=div(b,a)b/adiv(b,a)a\bsolve(a,b)

    众所周知,Python 提供了更好的解决方案,而无需 新符号:可以使该方法被延迟,以便 和 等价于 Matlab 的 和 。这 实现非常简单,生成的应用程序代码干净。inverse.Ia.I*bb*a.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)

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


对一般元素化的影响

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

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

  1. 按位运算符。目前,Python 为按位分配了六个运算符 运算:和 () 或 ()、xor ()、补码 ()、左 shift () 和右移 (),具有自己的优先级。&|^~<<>>

    其中,运营商可以看作是 适用于整数的格运算符的元素版本 位字符串:&|^~

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

    这些可以看作是一般的元素格算子,而不是 仅限于整数中的位。

    为了具有 的命名运算符,必须 做一个保留的词。xor~xorxor

  2. 列出算术。

    [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)]
  3. 列表推导。

    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)]
  4. 元组生成(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)]
  5. 使用通用元素元字符替换 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)
    ...
  6. Elementwise 格式运算符(带广播):

    a = [1,2,3,4,5]
    print ["%5d "] ~% a
    a = [[1,2],[3,4]]
    print ["%5d "] ~~% a
  7. 丰富的对比:

    [1, 2, 3]  ~< [3, 2, 1]  # [1, 0, 0]
    [1, 2, 3] ~== [3, 2, 1]  # [0, 1, 0]
  8. 丰富的索引:

    [a, b, c, d] ~[2, 3, 1]  # [c, d, b]
  9. 元组扁平化:

    a = (1,2);  b = (3,4)
    f(~a, ~b)                # f(1,2,3,4)
  10. 复制运算符:

    a ~= b                   # a = b.copy()
可以有特定级别的深拷贝:
a ~~= b                  # a = b.copy(2)

笔记

  1. 可能还有很多其他类似的情况。这种一般方法 似乎非常适合其中的大多数,而不是几个单独的扩展 对于它们中的每一个(并行和交叉迭代,列表推导,丰富 比较等)。

  2. elementwise 的语义取决于应用程序。例如,一个 矩阵的元素从列表的角度向下两个级别。 这需要比目前的提案更根本的改变。在任何 在这种情况下,目前的提案不会对未来产生负面影响 这种性质的可能性。

请注意,本部分介绍一种一致的未来扩展类型 与当前提案,但可能提供额外的兼容性或其他 问题。它们与当前提案无关。


对指定运算符的影响

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

  1. 命名中缀运算符。

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

    a @opname b == opname(a,b)

    提到的其他表示包括:

    .name ~name~ :name: (.name) %name%

    和类似的变化。不能使用基于纯括号的运算符 这边。

    这需要在解析器中进行更改才能识别并解析它 与函数调用相同的结构。所有这些的优先权 运算符必须固定在一个级别,因此实现将 与保持优先级的附加数学运算符不同 现有的数学运算符。@opname

    目前拟议的延期并不限制未来可能的延期 以任何方式以这种形式。

  2. 更通用的符号运算符。

    未来扩展的另一种形式是使用 meta 字符和 运算符符号(不能在语法结构中使用的符号 运算符除外)。假设是元字符。然后:@

    a + b,    a @+ b,    a @@+ b,  a @+- b

    都是具有优先级层次结构的运算符,定义如下:

    def "+"(a, b)
    def "@+"(a, b)
    def "@@+"(a, b)
    def "@+-"(a, b)

    与命名运算符相比,一个优势是更大的灵活性 基于元字符或普通运算符的优先级 符号。这也允许操作员组合。缺点是 它们更像是线路噪声。无论如何,目前的提案没有 影响其未来的可能性。

    当 Unicode 成为 已正式发布。

    请注意,本节讨论建议的扩展的兼容性 以及未来可能的扩展。这些的可取性或兼容性 此处未特别考虑其他扩展本身。


The End 微信扫一扫

文章声明:以上内容(如有图片或视频在内)除非注明,否则均为腾龙猫勺儿原创文章,转载或复制请以超链接形式并注明出处。

本文作者:猫勺本文链接:https://www.jo6.cn/post/99.html

上一篇 下一篇

相关阅读

发表评论

访客 访客
快捷回复: 表情:
评论列表 (暂无评论,92人围观)

还没有评论,来说两句吧...

取消
微信二维码
微信二维码
支付宝二维码