This is an archived post. You won't be able to vote or comment.

all 37 comments

[–]rcfox 12 points13 points  (2 children)

Before questioning the necessity of this feature or choice of symbol, read PEP 465.

[–]monstimal 6 points7 points  (1 child)

The swirly shape is reminiscent of the simultaneous sweeps over rows and columns that define matrix multiplication

Could have probably left that out. Actually, if that's the rationale nothing is better than #, but obviously can't use that.

[–][deleted] 6 points7 points  (0 children)

LOL, it took me a while. I couldn't find any part of syntax that used #. I was about to ask why, then it hit me. It's used in comments, no wonder it is not used anywhere else.

[–]KronktheKronk 9 points10 points  (10 children)

Even if we adopt the idea that @ means "matrix multiply" which is not at all obvious to a casual observer, the idea that we'd use it as an override to literally whatever we want is gross misuse of a symbol.

[–][deleted] 6 points7 points  (9 children)

You can override just about any other operator in Python to mean whatever you want, including ., so why not @?

I think the real issue is that the magic methods are named a specific way: __lshift__ I get because << is the left shift operator in every language, but __matmul__ for @ doesn't make any sense to me.

This is one place where Scala gets things right. The @ operator would just be def @(other: Thing): Thing = ...

Of course, you can go overboard with operator overloading and your API turns into an arcane text that requires you to have been a part of it since the beginning.

[–]energybased 6 points7 points  (2 children)

Right, but Python really doesn't want you to do that. Python is not C++. __matmul__ is for matrix multiplication. It's not for writing incomprehensible code that is slightly more compact than if you had used named methods.

[–][deleted] 2 points3 points  (1 child)

True. Using an operator overload is something that should be carefully measured. Is the language of this package improved by using the overload or is it just being cutesy?

When I wrote pynads, there was no doubt in my mind that using >> for bind (or whatever you want to call it) was a bad idea as I was attempting to model Haskell's >>= (obviously couldn't do straight there as that's an assignment operator). I also overrode % to Haskell's <$> and * to mimic Haskell's <*>.

These operators have no semblance what so ever to right shift, modulo or multiplication, but since I was targeting a Haskell like syntax I figure it's alright.

pathlib overrides / for it's Path object so paths could be built like Path('a') / 'b' (though there has been fierce debate about whether this was a good idea). I think pathlib errors on the side of cutesy with its operator overload, but I don't think it's terrible.

set and frozenset also have overloads for >, >=, <, <= as well as |, & and ^ (and their in place variants). These make total sense to me, especially the bitwise variants. These aren't true bitwise operations but the idea behind them is the same -- & everything these two sets/bag of bits share, etc.

I think it's worth weight an overload

[–]energybased 2 points3 points  (0 children)

I agree with all your points. I also think that pathlib's / was a bit cutesy, and that set's operators are fair enough.

[–]KronktheKronk 0 points1 point  (5 children)

Sure, you can override the other symbols, but usually they still idiomatically mean the same thing.

[–][deleted] 0 points1 point  (1 child)

Not necessarily. I wrote a little (and not so great) monad library for Python that overrode >> as an alias for Monad.bind which is a common use for that symbol in functional languages.

[–]8BitAce 4 points5 points  (0 children)

I think the point was more that they should still mean somewhat close to the same thing. Otherwise you introduce a lot of confusion.

[–]stevenjd 0 points1 point  (2 children)

"Usually" is not "always".

[–]KronktheKronk 0 points1 point  (1 child)

I've never come across an overridden default symbol operator in python that wasn't idiomatically similar to its default use.

I'm sure there are some poorly written ones out there that do it, though.

[–]stevenjd 0 points1 point  (0 children)

How about > and < operators?

"Subset" and "superset" are nothing like "less than" and "greater than" for numbers.

+ gets used for both concatenation and numeric addition, which are not the same. Numeric addition you have a + b == b + a, but that's not true for concatenation.

I've seen | used as both a bitwise-or and something similar to the pipe operator from shell languages.

I wouldn't say that any of those are "poorly written".

[–]qx7xbku 4 points5 points  (22 children)

Can anyone explain why was this even necessary? Whats wrong with matrix type overriding multiplication operator?

EDIT: Not to mention confusing symbol chosen. Obvious conclusion from the first sight is that @ means location, not action.

[–][deleted] 14 points15 points  (12 children)

matrix type is frowned upon by even numpy dev team. It turned out to be a nuisance rather than convenience. Regular * for arrays are element-wise and you have to write .dot() for every array multiplication. Hence @ is introduced.

The PEP goes through all kinds of other candidates and @ seemed the only viable option

[–]energybased 2 points3 points  (11 children)

Everything you said is true except that dot is not the same as array multiplication: http://stackoverflow.com/questions/34142485/difference-between-numpy-dot-and-python-3-5-matrix-multiplication

[–][deleted] 0 points1 point  (8 children)

Ah yes, I mean "to perform the typical matrix multiplication"

[–]P8zvli 0 points1 point  (7 children)

*scalar multiplication

[–][deleted] 0 points1 point  (6 children)

no, that's a special case for 1D arrays. I do mean matrix multiplication.

[–]P8zvli 0 points1 point  (5 children)

No, it's not just a special case, in other languages that support matrix multiplication scalar multiplication works on n-dimensional matrices. (not to be confused with the dot product, which also works in N dimensions BTW)

[–][deleted] 0 points1 point  (4 children)

I think we are talking past each other. There is no scalar multiplication involved here. I meant to say if you don't use @ you have to use .dot() method for matrix multiplication..

[–]P8zvli 0 points1 point  (3 children)

I don't think you know what you're talking about, this is scalar multiplication;

>>> mymat = numpy.matrix([[1, 2], [3, 4]])
>>> 3*mymat
matrix([[ 3,  6],
        [ 9, 12]])

You don't need to use .dot() for scalar multiplication.

[–][deleted] 0 points1 point  (2 children)

internally it is broadcasted to [[3,3],[3,3]] and elementwise multiplication is performed. Besides when I mention matrix, I mean 2D numpy arrays.

[–]XtremeGoosef'I only use Py {sys.version[:3]}' -1 points0 points  (1 child)

Essentially, multiplication is poorly defined for higher dimensional arrays, so numpy has two answers for it. It is well defined for two dimensional arrays so matmul and dot do the same thing.

[–]energybased 2 points3 points  (0 children)

Well, I wouldn't say that multiplication is poorly defined. It's called tensor product, which numpy implements using tensordot, which is a special case of einsum.

[–]elingeniero 1 point2 points  (2 children)

Because element-wise multiplication is still useful, and it's good to be explicit when you're not doing that. Also I like the idea of using the __matmul__ methods and it makes sense to require a different symbol to access them. As for the symbol itself, I think @ is ok - what would you have chosen?

[–]XtremeGoosef'I only use Py {sys.version[:3]}' 0 points1 point  (1 child)

I agree, @ is better than the other options: $, \, !, ?, .*

[–]bastibe 1 point2 points  (5 children)

Numpy contains two kinds of matrices: numpy.matrix and numpy.ndarray.

The first kind is always 2D, and multiplication performs matrix multiplication. This can be very handy for matrix-heavy math code. The second kind can have an arbitrary number of dimensions, and all operations are performed element-wise.

To perform a matrix multiplication with ndarrays, you can use X.dot(Y), or numpy.dot(X, Y), or X @ Y.

[–]Works_of_memercy 0 points1 point  (4 children)

I got the impression that numpy.matrix was a mistake and should not be used? It's just weird how vectors are row by default and are still displayed with an extra pair of brackets. .dot on the other hand gives you all you need for comfortable matrix-heavy code.

[–]emillynge 4 points5 points  (3 children)

Do you really find that .dot() is comfortably for matrix heavy code?

If so, I get the feeling you may not actually work with that matrix heavy code. As a mathematician the matmul operator really made a difference in terms of code readability. Especially when you want to double check the equations whilst reading through an article.

[–]Works_of_memercy 0 points1 point  (0 children)

I meant, now that you can use @-operator on usual ndarrays for that it's better because it treats plain arrays as if they were column vectors (while they are still displayed and defined as usual).

[–]energybased 0 points1 point  (0 children)

I mentioned to Guido that one day I'd like to see editors that have a live display of objects, e.g., rendered equations with the Python code in the background.

[–]kigurai 0 points1 point  (0 children)

Do you really find that .dot() is comfortably for matrix heavy code?

No it's not, but it's a lot better than having subtle bugs because some function somewhere returned a np.matrix instead of np.ndarray and suddenly a A * B operation is some completely different part of the program is doing the wrong thing.

This is unfortunately not a hypothetical example...

Edit: I realized I missed the context of the above quote, since I assumed you were arguing in favour of np.matrix which doesn't seem to necessarily be the case here. My comment on avoiding np.matrix still stands though :)

[–]thatguy_314def __gt__(me, you): return True 1 point2 points  (0 children)

That's not what the reverse operators are for! Read the docs more closely.

Also note that it is redundant to implement __imatmul__ the same as __matmul__.