【问题标题】:Is it bad form to override the "dot" operator in Python?覆盖 Python 中的“点”运算符是不好的形式吗?
【发布时间】:2018-08-25 14:34:19
【问题描述】:

通常,Python 中的句点表示类成员身份:

class A:
    a = 1

>>> A.a
1

不过,有时该语言似乎不够灵活,无法完全表达计算机科学以外领域的想法。考虑以下示例(为简洁起见,相当脆弱)使用相同的运算符看起来完全不同。

class Vector:
    def __init__(self, data):
        self.data = list(data)

    def dot(self, x):
        return sum([a*b for a, b in zip(self.data, x.data)])

    def __getattr__(self, x):
        if x == 'Vector':
            return lambda p: self.dot(Vector(p))
        return self.dot(globals()[x])

在这里,我们接管了__getattr__(),因此在 Python 会尝试从我们的向量中查找属性的许多场景中,它会计算数学点积。

>>> v = Vector([1, 2])
>>> v.Vector([3, 4])
11

>>> v.v
5

如果将此类行为的范围限制在感兴趣的领域,这样的设计模式有什么问题吗?

【问题讨论】:

  • 当我们有 numpy 时,为什么要在 python 中实现这个?我看不到弄乱语法的好处
  • 我想很多人都会同意,将 . 从其属性访问的标准含义中重新用作对 raised 点的粗略模仿会降低代码的可读性.有些人认为使用+ 进行数字加法和列表连接是错误的;这是一个更加不同的重载。
  • 在 Stack Overflow 问题中与人争论语言设计有点奇怪。并非每个字符都很重要,如果您重复使用特定功能,我的评估是您可能遇到了一个架构问题,您没有编写可重用的库。
  • @roganjosh Numpy 是一个重量级的依赖,如果你愿意,你可以做同样的事情来修改 numpy 的行为。另外,这是一个玩具示例。我有一个示例,其中包含树和图表的语法,事后看来可能没有受到如此负面的评价。

标签: python design-patterns syntax


【解决方案1】:

这是个坏主意。

为什么?因为你所说的“点运算符”并不是真正的运算符。这是因为右侧的“操作数”被解释为字符串,而不是表达式。这对您来说可能看起来微不足道,但它会产生很多有问题的后果:

  • Python 程序员习惯于foo.bar 的意思是“取foo 对象的bar 属性”。将点转换为点积运算符打破了这种期望,并且会使阅读您的代码的人感到困惑。不直观。

  • 这是模棱两可的,因为您无法知道用户是在尝试计算点积还是访问属性。考虑:

    >>> data = Vector([1, 2])
    >>> v.data  # dot product or accessing the data attribute?
    

    请记住,方法也是属性:

    >>> dot = Vector([1, 2])
    >>> v.dot  # dot product or accessing the dot method?
    
  • 因为右边的操作数被解释为一个字符串,你必须跳过一大堆圈才能把那个字符串变成有用的东西——就像你试图用globals()[x]做的那样,它会查找一个变量在全球范围内。问题是 - 在某些情况下 - 完全不可能仅通过变量名来访问变量。无论您做什么,您都将永远无法访问不再存在的变量,因为它已经被垃圾回收了:

    def func():
        v2 = Vector([1, 2])
    
        def closure_func():
            return v.v2  # this will never work because v2 is already dead!
    
        return closure_func
    
    closure_func = func()
    result = closure_func()
    
  • 因为右边的操作数是一个字符串,所以不能在右边使用任意表达式。您仅限于变量;尝试在右侧使用其他任何东西都会引发某种异常。更糟糕的是,它甚至不会像其他运算符那样抛出适当的TypeError

    >>> [] + 1
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: can only concatenate list (not "int") to list
    >>> v.1
      File "<stdin>", line 1
        v.1
          ^
    SyntaxError: invalid syntax
    
  • 与真正的运算符不同,“点运算符”只能在左侧操作数中实现。所有其他运算符可以在两个相应的 dunder 方法之一中实现,例如 + 运算符的 __add____radd__。示例:

    >>> class Incrementer:
    ...     def __radd__(self, other):
    ...         return other + 1
    ... 
    >>> 2 + Incrementer()
    3
    

    这对于您的点积是不可能的:

    >>> my_v = MyCustomVector()
    >>> v.my_v
    AttributeError: 'MyCustomVector' object has no attribute 'data'
    

底线:在您的 Vector 类中实现 dot 方法是可行的方法。由于 dot 不是真正的运算符,因此试图将其变成一个必然会适得其反。

【讨论】:

  • GC 示例不是超级适用的,因为您也不能在 GC 对象上调用 v.dot(v2),因为在这种情况下 v2 将在 locals() 中。如果您修改代码以使v2 不在本地范围内(例如,在尝试在该范围内访问它后在本地范围内的函数中设置v2),那么普通代码将无法访问v2无论如何,额外的工作。额外的工作是nonlocal 关键字,它将把v2 带回locals()。在所有情况下,如果变量可访问,则它位于 locals()globals() 中。否则一切看起来都很好。
  • @HansMusgrave 重点是v.v2 不包含对v2 对象的引用(因为v2 只是一个代表属性名称的字符串),所以你'即使您仍在计划使用它,v2 也总是面临被垃圾收集的危险。
  • GC 应该很容易理解。我错过了什么?代码v.dot(v2) 不包含对v2 对象的引用,直到该行被解释为止,因此就GC而言,区别在于v.dot(v2)之间,其中引用计数会随着参数的增加而增加通过,v.dot(globals()['v2']),直到在方法调用v.__getattr__('v2') 之后,您才真正调用globals()['v2'],因此如果v2 在两次调用之间取消引用其对象,则垃圾收集可能会在两者之间运行。还会有更长的差距吗?
  • @HansMusgrave 我不确定我是否理解你所说的正确,但差距的持续时间并不重要。存在 间隙这一简单事实意味着对象有可能被垃圾回收。如果您查看更新后的代码 sn-p,您会看到它现在将 v2 封装在一个闭包中(好吧,它会尝试这样做)。但是由于v.v2实际上并没有引用v2对象,所以它的引用计数在return closure_func之后立即下降到0并被销毁。无论你何时拨打closure_funcv2都会消失。
  • 闭包示例很好,我没有考虑过。它仍然可以通过nonlocal 进行纠正,但它是一个很好的具体示例,说明由于 GC,修改会产生意想不到的副作用以及有问题的解决方法。谢谢。
【解决方案2】:

我不会推荐它。 “语言不够灵活,无法表达想法”是什么意思?在您的示例中,v.dot(u) 具有表现力并具有预期的效果。顺便说一句,这正是numpy 的做法。

【讨论】:

  • 按照同样的逻辑,v.add(u)v.sub(u)v.mul(u) 等具有表现力并具有预期的效果。就此而言,当我们可以使用适当的方法调用显式构建这些查询时,为什么还要使用正则表达式呢?我们为这些用例提供了运算符,因为当您大量使用某个功能时,每个字符都会有所帮助。
  • "按照同样的逻辑,v.add(u)、v.sub(u)、v.mul(u) 等具有表现力并具有预期的效果。"是的。
【解决方案3】:

如果你想使用向量,有一个大部分没有提到的特殊方法名称,叫做__matmul__。这带有相应的就地和反射方法__imatmul____rmatmul__。操作员是@

a @ b
# corresponds to
a.__matmul__(b)

【讨论】:

  • 矩阵乘法更类似于叉积而不是点积。如果您尝试支持向量运算,最好将* 用于点积和标量积,将@ 用于叉积。
  • 已确认。我在学校没有太多涉及矢量
猜你喜欢
  • 1970-01-01
  • 2011-01-14
  • 1970-01-01
  • 2010-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多