A Quantity is collections.abc.Iterable
When checking if a Quantity is iterable using isinstance(q, collections.abc.Iterable), the result on the quantity is different from the one of its .value : this is not a good since we want quantity object to behave like their values.
From the point of view of np.iterable and iter, both the quantity and its value behave the same - hence it is recommended to use these methods.
Nevertheless, it'd be greate that collections.abc.Iterable gives identical results.
The origin of the differences in behavior is that __iter__ method must be defined on Quantity for compatibility. Iteable just checks the existence of the __iter__(), while np.iterable actually tries that method on the object and checks if TypeError is returned. Given the implementation of collections.abc.Iterable, the only solution would be to remove the __iter__ method, but that's not possible AFAIK.
As a workaround, it then recommended to use np.iterable(x) and not collections.abc.Iterable(x) when dealing with Quantity.
In details :
np.iterable : According to the doc This function is used by matplotlib units interface, so we definitely need it. It is said that In most cases, the results of np.iterable(obj) are consistent with isinstance(obj, collections.abc.Iterable), which is not true in our case. The function just check if iter(x) raises a TypeError or not :
try:
iter(y)
except TypeError:
return False
return True
collections.abc.Iterable : According to the doc, the test basically consists in detecting a __iter__() method, which Quantity does have (but not int and float for eg.), hence the different behavior:
class collections.abc.Iterable¶
ABC for classes that provide the __iter__() method.
Checking isinstance(obj, Iterable) detects classes that are registered as Iterable or that have an __iter__() method, but it does not detect classes that iterate with the __getitem__() method. The only reliable way to determine whether an object is iterable is to call iter(obj).
Full example :
import collections.abc
import numpy as np
from physipy import m
q = 2.3 * m
# collections.abc.Iterable
print('collections.abc.Iterable')
print('m', isinstance(m, collections.abc.Iterable)) # False
print('1', isinstance(m.value, collections.abc.Iterable)) # True
print('q', isinstance(q, collections.abc.Iterable)) # False
print('2.3', isinstance(q.value, collections.abc.Iterable)) # True
# int and float are not Iterable
# but m and q are
# np.iterable
print("\nnp.iterable")
print('m', np.iterable(m))
print('1', np.iterable(m.value))
print('q', np.iterable(q))
print('2.3', np.iterable(q.value))
# all equivalent from np.iterable pov
# which is a good thing
# iter
print("\niter")
for name, val in [('m', m), ('1', m.value), ('q', q), ('2.3', q.value)]:
s = name + ' '
try:
iter(q)
except TypeError:
s += str(False) + ' (on TypeError)'
else:
s += str(True)
print(s)
# all equivalent from iter pov
# which is a good thing
collections.abc.Iterable
m True
1 False
q True
2.3 False
np.iterable
m False
1 False
q False
2.3 False
iter
m False (on TypeError)
1 False (on TypeError)
q False (on TypeError)
2.3 False (on TypeError)
A
Quantityiscollections.abc.IterableWhen checking if a
Quantityis iterable usingisinstance(q, collections.abc.Iterable), the result on the quantity is different from the one of its.value: this is not a good since we want quantity object to behave like their values.From the point of view of
np.iterableanditer, both the quantity and its value behave the same - hence it is recommended to use these methods.Nevertheless, it'd be greate that
collections.abc.Iterablegives identical results.The origin of the differences in behavior is that
__iter__method must be defined onQuantityfor compatibility.Iteablejust checks the existence of the__iter__(), whilenp.iterableactually tries that method on the object and checks ifTypeErroris returned. Given the implementation ofcollections.abc.Iterable, the only solution would be to remove the__iter__method, but that's not possible AFAIK.As a workaround, it then recommended to use
np.iterable(x)and notcollections.abc.Iterable(x)when dealing withQuantity.In details :
np.iterable: According to the doc This function is used by matplotlib units interface, so we definitely need it. It is said thatIn most cases, the results of np.iterable(obj) are consistent with isinstance(obj, collections.abc.Iterable), which is not true in our case. The function just check ifiter(x)raises aTypeErroror not :collections.abc.Iterable: According to the doc, the test basically consists in detecting a__iter__()method, whichQuantitydoes have (but notintandfloatfor eg.), hence the different behavior:Full example :