P. Camilleri
P. Camilleri

Reputation: 13218

Substracting number from list not raising TypeError

If I do

a = [1, 2, 7]
a - a[-1]

I get TypeError: unsupported operand type(s) for -: 'list' and 'int'

However, I have a list b made of np.float64, and the following code works:

type(b)
# list
b - b[-1]
# array([ 281.04209146,    6.57013103,    0.        ])

I think this is because the numbers in b are np.float64, and b is somewhat cast to np.array, then broadcasting happens. But I still found this behavior surprising: since all elements in a list do not need to have the same type, what if b[0] had been a string ? The operands in b - b[-1] would still have been list and np.float64, so why is b - b[-1] not raising a TypeError?

EDIT: Someone answered saying that list and np.array are different; well, I know. But there b is not a np.array. It is behaving like one, but its type is list, like I've stated in the code snippet.

Here's a minimal working example for b:

b
# [1598.717274996219, 1324.245314569733, 1317.6751835362861]
type(b[0])
# numpy.float64

Upvotes: 2

Views: 251

Answers (2)

hpaulj
hpaulj

Reputation: 231335

In an expression like A-B, the interpreter can either implement it as A.__sub__(B) or B.__rsub__(A).

Lists implement mul and add but not sub

In [29]: [1,2,3]*3
Out[29]: [1, 2, 3, 1, 2, 3, 1, 2, 3]
In [30]: [1,2,3]+[1]
Out[30]: [1, 2, 3, 1]
In [31]: [1,2,3]-3
TypeError: unsupported operand type(s) for -: 'list' and 'int'

np.ndarray implements a __rsub__

In [32]: [1,2,3]-np.array([1,2,3])
Out[32]: array([0, 0, 0])
# np.array([1,2,3]).__rsub__([1,2,3])

And that method tries to turn the LHS into an array, so this expression is the same as:

In [33]: np.asarray([1,2,3]) - np.array([1,2,3])
Out[33]: array([0, 0, 0])

If the list contains strings, this fails:

In [35]: ['a',2,3]-np.array([1,2,3])
TypeError: ufunc 'subtract' did not contain a loop with signature matching types dtype('<U11') dtype('<U11') dtype('<U11')

because that list becomes an array of strings:

In [36]: np.asarray(['a',2,3])
Out[36]: 
array(['a', '2', '3'], 
      dtype='<U1')

and array subtraction with string dtype is not implemented.

All that I wrote with the array RHS applies if it is np.float64. np.float64(10) behaves (in most contexts) the same as np.array(10.0).

So all these subtractions with b are the same:

 b = [np.float64(10), np.float64(1)]
 b - b[-1]
 b - np.float64(1)
 b - np.array(1.0)
 np.array(b) - np.array(1.0)

In sum, if the RHS is some sort of array, it turns the LHS list into an array as well. From there it's a question of whether the 2 arrays are compatible (in shape and dtype).

Upvotes: 2

Daniel F
Daniel F

Reputation: 14399

You don't get the same TypeError when you make b[0] a string though.

b=[np.float_(a_) for a_ in a]

b
Out[4]: [1.0, 2.0, 7.0]

b-b[-1]
Out[5]: array([-6., -5.,  0.])

b[0]='a'

b
Out[7]: ['a', 2.0, 7.0]

b-b[-1]
Traceback (most recent call last):

  File "<#>", line 1, in <module>
    b-b[-1]

TypeError: ufunc 'subtract' did not contain a loop with signature matching types dtype('<U32') dtype('<U32') dtype('<U32')

You get a TypeError for mismatching types in subtract. In the original, when you get a np.float64 for b[-1], it casts the whole expression to numpy arrays. So if b[-1] is a string:

b[2]='a'

b-b[-1]
Traceback (most recent call last):

  File "<#>", line 1, in <module>
    b-b[-1]

TypeError: unsupported operand type(s) for -: 'list' and 'str'

you're back to the original TypeError.

Upvotes: 1

Related Questions