Use of power on list

current scenario -

multiplication works for list, that is,

[1, 2] * 2

gives,

[1, 2, 1, 2]

but power does not work, that is,

[1, 2] ** 2

gives error,

TypeError: unsupported operand type(s) for ** or pow(): 'list' and 'int'

I checked for all builtins, power only works on these,

defaultdict(<class 'set'>,
            {'bool': {'int', 'float', 'complex', 'bool'},
             'complex': {'int', 'float', 'complex', 'bool'},
             'float': {'int', 'float', 'complex', 'bool'},
             'int': {'int', 'float', 'complex', 'bool'}})

(means, int ** int, int ** float, int ** complex, int ** bool is valid)

expected scenario -

power works for list in the following way,

[1, 2] ** 0 -> [1, 2]

# it should be based on length of list, since there are two elements, 
# so the below one is like, [1, 2] * (2 ** 1)
[1, 2] ** 1 -> [1, 2, 1, 2] 

# [1, 2] * (2 ** 2), therefore repeated four times
[1, 2] ** 2 -> [1, 2, 1, 2, 1, 2, 1, 2] 

similarly,

[1, 2, 3] ** 0 -> [1, 2, 3]
[1, 2, 3] ** 1 -> [1, 2, 3, 1, 2, 3, 1, 2, 3]
[1, 2, 3] ** 2 -> [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
1 Like

The current logic actually makes a lot of sense mathematically and intuitively.

The most basic operation, addition, is very intuitive: [1, 2] + [3, 4] == [1, 2, 3, 4]
Multiplication is a bit less intuitive but remember that in arithmetics any multiplication can be converted to addition: [1, 2] * 2 == [1, 2] + [1, 2] == [1, 2, 1, 2]
Now, powers have the same trick of being easily converted to multiplication. Let’s try that:
[1, 2] ** 2 == [1, 2] * [1, 2]

but here we come to a problem: multiplication is not defined on lists. What you propose is to make the following equivalent:
[1, 2] * 3 and [1, 2] * [1, 2, 3]
but as you see, it’s not intuitive at all anymore.

Problem is: if we want to multiply lists, then we need a specific method of doing so. And there is more than one way of doing so (dot product vs cross product). numpy/pandas give you some of these ways with their arrays. Which one should python pick as the default one? The more commonly used one? I don’t know. I don’t think anybody knows.

So to conclude, power operations on lists are unintuitive and arbitrary which seems to be why whoever implemented lists has decided to omit them.

4 Likes

We already have list replication:

[1, 2]*2  # returns [1, 2, 1, 2]

Why do you want list ** operator (which is not exponentiation or power, because list * operator is not multiplication, it is replication) to do the same thing?

[1, 2]**1  # why should this return [1, 2, 1, 2] ???

This makes no sense for replication. It makes even less sense if we call the proposed list ** operator “exponentiation”:

(1) An exponent of 0 must return the multiplicative identity element, which for numbers is 1. 1 is the multiplicative identity element, because x*1 == x.

So list “exponentiation” L**0 must return the int 1, since L*1 == L.

(2) An exponent of 1 must return the other operand: x**1 == x.

So list “exponentiation” L**1 must return L.

(3) An exponent of 2 must be equivalent to multiplication by itself: x**2 == x*x. But list “multiplication” doesn’t accept list*list multiplication, so squaring a list is meaningless.

This brings us back to where I started: the * operator for lists is not multiplication, it is replication, it replicates the elements in the list. There is no sensible way to extend that to exponentiation.

2 Likes

yes I think the op exponentiation is a bit wrong.
one variation could be,

[1, 2] ** 0 -> 1
[1, 2] ** 1 -> [1, 2]
[1, 2] ** 2 -> [1, 2] * [1, 2] -> [1*1, 2*2] = [1, 4]
or
[1, 2] ** 2 -> [1, 2] * [1, 2] -> [1*1, 1*2, 2*1, 2*2] = [1, 2, 2, 4] 

but as mentioned in the second post, there are multiple ways to do this product.

but still I think it should not throw an error, and instead implement one of the multiple possible ways to multiply two lists.

How would that work if the list is filled with types that do not provide a __mul__? I agree with Steven that list ‘exponentiation’ does not make sense. The fact that even multiplication works is a bit sketchy imo. It’s nice as a shorthand, but severe abuse of notation that is not totally obvious.

based on my inspection, the *, it could be either multiplication or replication only works in the following cases,

{'bool': {'bool', 'bytearray', 'bytes', 'complex', 'float', 'int', 'list', 'str', 'tuple'},
 'bytearray': {'int', 'bool'},
 'bytes': {'int', 'bool'},
 'complex': {'int', 'float', 'bool', 'complex'},
 'float': {'int', 'float', 'bool', 'complex'},
 'int': {'bool', 'bytearray', 'bytes', 'complex', 'float', 'int', 'list', 'str', 'tuple'},
 'list': {'int', 'bool'},
 'str': {'int', 'bool'},
 'tuple': {'int', 'bool'}})

means list could be multiplied with int, or a bool and so on.
any other types in a list, then the list * list multiplication would fail.

It seems to work on all builtin sequence types which, as I said, makes sense given that it’s just repeated addition.

Why do you think it is better to implement an arbitrary multiplication instead of throwing an error? If the user wants some specific type of multiplication, the user can implement it themselves by inheriting from list/tuple/deque/str and creating a dunder method.

Just for the sake of a fun argument: why do you distinguish between multiplication and replication?

To me it seems that lists just support integer multiplication due to their support of addition with other lists. I.e. it is still full blown multiplication but with a single datatype. But maybe I misunderstand something here.

Lists support concatenation, not addition.

5 + 3      # returns 8, not 53
[5] + [3]  # returns [5, 3] not [8]

In mathematics, there is a very strong (but not universal) convention for the + sign to be used for operations that are commutative, such as ordinary addition: 5 + 3 == 3 + 5. Two exceptions are near rings and ordinal arithmetic.

Sequence concatenation is not commutative, and I can think of at least one language that uses & for string concatenation for that reason.

[5] + [3] != [3] + [5]
"a" + "b" != "b" + "a"

The Python docs officially describe + and * for sequences as concatenation and replication.

I acknowledge that concatentation and addition are related, but the differences are important too.

5 Likes

Great explanation. TIL a bit more about mathematics :slight_smile:

This is a beautiful answer, Stanislav.

Regarding…

…may I suggest slightly different wording:

…multiplication between list objects is not defined.

1 Like

@Ovsyanka answered…

Problem is: if we want to multiply lists [with other lists], then we need a specific method of doing so. And there is more than one way of doing so (dot product vs cross product).

…and your idea is a third way to interpret list exponentiation.

This is a matter of what result we’re (you’re) trying to create and what is the best way to achieve it.

Pandas and NumPy have functions to manipulate dataframes and matrices. You could also build some custom iteration funtions to achieve whatever manipulation you might consider “list exponentiation” to be. Someone might draft a PEP on it–or possibly already has The problem is that the community will probably have a hard time settling on such a definition and, as Steven pointed out, we probably aren’t well served by creating a monster called “List Multiplication” or “List Exponentiation” because these are inherently arbitrary and meaningless terms.

I agree that replication is the root operation for lists. Maybe we could call them (*) "iterative replication" and (**) "cross replication".

As @steven.daprano said, the ‘’ operator isn’t multiplication for lists. It’s replication. ‘+’ and '’ are not in a mathematical context in the case of lists, so are not arithmetic operators here. While we strive for ge eral consistency, we have a dinite number od aymbols to choose from. Particular Greek letters are used in many different contexts and have distictly different meanings in each of those contexts.

The deal breaker for mathematical operations on lists, as shown by several answers, is that a list is a list of arbitrary, unknown objects. We can only replicate objects (clone them or create extra pointers to them), not multiply them.

Great topic!

One relatively sane option would be to have list_ ** n be equivalent to itertools.product(list_, repeat=n). It’s still a bit problematic, though, because you’d then want to define l1 * l2 to be itertools.product(l1, l2), and that’s not associative, which is a bit of a betrayal of *. Still, it would be nice to be able to write

for foo, bar in foos * bars:
    do_stuff()

How often do you use itertools.product(list_, repeat=n)?

Unless it is really common, it doesn’t deserve to be an operator. Named functions are much easier to read than obscure operators.

We use operators for really common operations like

  • numeric addition, multiplication, division etc;
  • bitwise AND, OR, XOR etc;
  • comparisons like equality, less than, etc.

Of course any programming language could invent arbitrary operators to do any operation they like, but unless people are using it all the time, it is going to just look terse and cryptic.

Code is read far more often than it is written. The five seconds I might save from writing

mylist**2

instead of explicitly using itertools.product will be outweighed a thousand times by the two or three minutes each spent by thousands of people reading it and having to work out what on earth it does.

This is why APL has not lasted as a popular language:

x[⍋x←6?40]

compared to Python:

sorted(random.sample(range(1, 40+1), 6))

The APL code is shorter, but which is easier to understand?

1 Like

Grepping through my documents gives a precise answer to your first question: four times (out of tens but not hundreds of thousands of lines of code).

I agree that it’s not worth the readability cost. I was thinking along the lines of: if Python had to define the power operator on lists, that might be the best definition to have.

A nice article on a similar topic that was already mentioned somewhere on discuss.python.org: It Ain't No Repeated Addition

I do not think that the article is nice. I think the writer has a very limited view of the subject. Multiplication is repeated addition for integer numbers.

The article was linked here:
https://discuss.python.org/t/deprecate-old-style-iteration-protocol/17863/9?u=vbrozik

I agree with the argumentation of Steven D’Aprano:
https://discuss.python.org/t/deprecate-old-style-iteration-protocol/17863/15?u=vbrozik

Note that both the posts were flagged as off-topic.