在Python中覆盖"点"运算符是不好的形式?

Han*_*ave 4 python syntax design-patterns

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

class A:
    a = 1

>>> A.a
1
Run Code Online (Sandbox Code Playgroud)

有时,语言似乎不够灵活,无法从计算机科学以外的领域完全表达一个想法.考虑下面的例子(为了简洁起见,相当简单)使用相同的运算符看起来像完全不同的东西.

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])
Run Code Online (Sandbox Code Playgroud)

在这里我们已经接管了,__getattr__()以便在Python尝试从我们的向量中找到属性的许多场景中,它改为计算数学点积.

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

>>> v.v
5
Run Code Online (Sandbox Code Playgroud)

如果这种行为的范围受限于感兴趣的领域,那么这种设计模式是否有任何问题?

Ara*_*Fey 11

这是个坏主意.

为什么?因为你所谓的"点运算符"并不是真正的运算符.那是因为右侧的"操作数"被解释为字符串,而不是表达式.这对你来说似乎微不足道,但它有很多问题后果:

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

  • 这是不明确的,因为您无法知道用户是否正在尝试计算点积或访问属性.考虑:

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

    请记住,方法也是属性:

    >>> dot = Vector([1, 2])
    >>> v.dot  # dot product or accessing the dot method?
    
    Run Code Online (Sandbox Code Playgroud)
  • 因为右手操作数被解释为一个字符串,所以你必须跳过一大堆箍,把这个字符串变成有用的东西 - 正如你试图做的那样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()
    
    Run Code Online (Sandbox Code Playgroud)
  • 因为右侧操作数是一个字符串,所以不能在右侧使用任意表达式.你只限于变量; 尝试在右侧使用其他任何东西都会引发某种异常.更糟糕的是,它甚至不会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
    
    Run Code Online (Sandbox Code Playgroud)
  • 与实数运算符不同,"点运算符"只能在左侧操作数中实现.所有其他运算符可以在两个相应的dunder方法中的任何一个中实现,例如__add__,__radd__对于+运算符.例:

    >>> class Incrementer:
    ...     def __radd__(self, other):
    ...         return other + 1
    ... 
    >>> 2 + Incrementer()
    3
    
    Run Code Online (Sandbox Code Playgroud)

    您的点积不能实现这一点:

    >>> my_v = MyCustomVector()
    >>> v.my_v
    AttributeError: 'MyCustomVector' object has no attribute 'data'
    
    Run Code Online (Sandbox Code Playgroud)

结论:dot在您的Vector课程中实施方法是可行的方法.由于点不是真正的运算符,因此尝试将其转换为一个运算符必然会适得其反.