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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With