Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it bad form to override the "dot" operator in Python?

Usually, a period in Python denotes class membership:

class A:
    a = 1

>>> A.a
1

Sometimes the language doesn't seem quite flexible enough to completely express an idea from domains outside of computer science though. Consider the following example which (fairly brittly for brevity) uses the same operator to seem like something completely different.

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])

Here we've taken over __getattr__() so that in many scenarios where Python would attempt to find an attribute from our vector it instead computes the mathematical dot product.

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

>>> v.v
5

If such behavior is kept restricted in scope to the domain of interest, is there anything wrong with such a design pattern?

like image 867
Hans Musgrave Avatar asked Dec 18 '22 21:12

Hans Musgrave


1 Answers

It's a bad idea.

Why? Because the "dot operator", as you call it, isn't really an operator to begin with. That's because the "operand" on the right-hand side is interpreted as a string, not as an expression. This may seem insignificant to you, but it has plenty of problematic consequences:

  • Python programmers are used to foo.bar meaning "Take the bar attribute of the foo object". Turning the dot into a dot product operator breaks this expectation and will confuse people who read your code. It's unintuitive.

  • It's ambiguous, because you cannot know if the user is trying to calculate a dot product or access an attribute. Consider:

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

    Keep in mind that methods are attributes, too:

    >>> dot = Vector([1, 2])
    >>> v.dot  # dot product or accessing the dot method?
    
  • Because the right-hand operand is interpreted as a string, you have to jump through a whole bunch of hoops to turn that string into something useful - as you've tried to do with globals()[x], which looks up a variable in the global scope. The problem is that - in certain situations - it's completely impossible to access a variable just by its name. No matter what you do, you will never be able to access a variable that no longer exists because it's already been garbage collected:

    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()
    
  • Because the right-hand operand is a string, you cannot use arbitrary expressions on the right-hand side. You're limited to variables; trying to use anything else on the right-hand side will throw some kind of exception. And to make it worse, it won't even throw the appropriate TypeError like other operators would:

    >>> [] + 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
    
  • Unlike real operators, the "dot operator" can only be implemented in the left-hand operand. All other operators can be implemented in either one of two corresponding dundermethods, for example __add__ and __radd__ for the + operator. Example:

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

    This isn't possible with your dot product:

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

Bottom line: Implementing a dot method in your Vector class is the way to go. Since the dot isn't a real operator, trying to turn it into one is bound to backfire.

like image 59
Aran-Fey Avatar answered Jan 05 '23 01:01

Aran-Fey