在Python中重写“点”运算符是否不好?

时间:2018-08-25 14:34:20

标签: python design-patterns syntax

通常,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

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

3 个答案:

答案 0 :(得分:11)

好主意。

为什么?因为您所说的“点运算符”实际上并不是一个运算符。这是因为右侧的“操作数”被解释为字符串,而不是表达式。这对您来说似乎微不足道,但会带来很多问题:

  • Python程序员习惯于foo.bar的意思是“采用bar对象的foo属性”。将点变成点乘运算符会破坏这种期望,并使读取您的代码的人感到困惑。这是不直观的。

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

    >>> 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'
    

底线:在dot类中实现Vector方法是必须的方法。由于该点不是真正的运算符,因此将其转换为一个点势必会事与愿违。

答案 1 :(得分:0)

我不推荐。 “语言不够灵活,无法表达想法”是什么意思?在您的示例中,v.dot(u)是富有表现力的,并且具有理想的效果。顺便说一下,这就是numpy的工作方式。

答案 2 :(得分:0)

如果要使用向量,则有一个特殊的方法名称,在大多数情况下都没有提及,名为__matmul__。这带有其相应的就地和反射方法__imatmul____rmatmul__。运算符是@

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