Skip to content

A Quantity is collections.abc.Iterable #3

Description

@mocquin

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions